-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Improve possible_borrower
#9701
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
Conversation
r? @giraffate (rust-highfive has picked a reviewer for you, use r? to override) |
☔ The latest upstream changes (presumably #9674) made this pull request unmergeable. Please resolve the merge conflicts. |
Hey, thank you for the PR. Could you maybe expand on the changelog entry a bit and explain how you specific improved it? 🙃 |
@xFrednet Please tell me if what I have now suffices. |
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. |
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 @smoelius Thank you for taking the time to figure this out and also to document this discussion! |
That should be good enough :), thank you! |
Going to take over this. r? @Jarcho Unless @giraffate has any objections. |
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()); |
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.
Can this not just be a Vec
? I don't see a reason to process the borrowers in order 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.
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.
204bed7
to
9d1cb71
Compare
Sorry for the delay, @Jarcho. I rebased and addressed your comments thus far. |
Thank you. @bors r+ |
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 -->
💔 Test failed - checks-action_test |
9d1cb71
to
4dbd8ad
Compare
Thank you, @Jarcho, and sorry for the trouble. I rebased and pushed what I think is a fix for the build failure. |
@bors retry |
@bors r=Jarcho Bors removes the approval, after any changes to the last commit. This should start the run :) |
☀️ Test successful - checks-action_dev_test, checks-action_remark_test, checks-action_test |
Thanks again, @Jarcho. |
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 <[email protected]> Commit-Queue: Auto-Submit <[email protected]> Reviewed-by: Joseph Ryan <[email protected]> Reviewed-by: Marc Khouri <[email protected]> Reviewed-by: Steven Grady <[email protected]> Fuchsia-Auto-Submit: Dan Johnson <[email protected]>
This PR makes several improvements to
clippy_uitls::mir::possible_borrower
. These changes benefit bothneedless_borrow
andredundant clone
.MaybeStorageLive
analysisI 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.
PossibleBorrower
a dataflow analysis instead of a visitorThe 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:
We would like to flag the
&
pointed to by the^
for removal.foo
's MIR begins like this:The call to
diag.note
appears inbb1
on the line beginning with_3 =
. TheString
is owned by_6
. So, in the call todiag.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
.only_borrowers
toat_most_borrowers
possible_borrowers
exposed a functiononly_borrowers
that determined whether the borrowers of some local were exactly some setS
. But, from what I can tell, this was overkill. For the lints that currently usepossible_borrower
(needless_borrow
andredundant_clone
), all we really want to know is whether there are borrowers other than those inS
. (Put another way, we only care about the subset relation in one direction.) The new functionat_most_borrowers
takes this more tailored approach.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
(previouslyonly_borrowers
), the size of the set of borrowersS
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 byclippy_uitls::mir::possible_origin
(a kind of "subroutine" ofpossible_borrower
).cc: @Jarcho
changelog: [
needless_borrow
], [redundant_clone
]: Now track references better and detect more cases#9701