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

Extend indexing_slicing lint #2790

Merged
merged 7 commits into from
Jun 21, 2018
Merged

Extend indexing_slicing lint #2790

merged 7 commits into from
Jun 21, 2018

Conversation

shnewto
Copy link
Contributor

@shnewto shnewto commented May 23, 2018

Hey there clippy team! I've made some assumptions in this PR and I'm not at all certain they'll look like the right approach to you. I'm looking forward to any feedback or revision requests you have, thanks!

Prior to this commit the indexing_slicing lint was limited to indexing/slicing operations on arrays. This meant that the scope of a really useful lint didn't include vectors. In order to include vectors in the indexing_slicing lint a few steps were taken.

The array_indexing.rs source file in clippy_lints was renamed to indexing_slicing.rs to more accurately reflect the lint's new scope. The OUT_OF_BOUNDS_INDEXING lint persists through these changes so if we can know that a constant index or slice on an array is in bounds no lint is triggered.

The array_indexing tests in the tests/ui directory were also extended and moved to indexing_slicing.rs and indexing_slicing.stderr.

The indexing_slicing lint was moved to the clippy_pedantic lint group.

A specific "Consider using" string was added to each of the indexing_slicing lint reports.

At least one of the test scenarios might look peculiar and I'll leave it up to y'all to decide if it's palatable. It's the result of indexing the array x after let x = [1, 2, 3, 4];

error: slicing may panic. Consider using `.get(..n)`or `.get_mut(..n)`instead
  --> $DIR/indexing_slicing.rs:23:6
   |
23 |     &x[0..][..3];
   |      ^^^^^^^^^^^

The error string reports only on the second half's range-to, because the range-from is in bounds!

Again, thanks for taking a look.

Closes #2536

@Manishearth
Copy link
Member

Could you split up the commit so that the file being renamed is a separate commit from the other changes? It's hard to review otherwise, github just shows it as a file being deleted and a different one being created.

Though it might be worth reviewing from scratch anyway 🤷‍♀️

@shnewto
Copy link
Contributor Author

shnewto commented May 23, 2018

@Manishearth I'll take a stab at it, forgive me if things end up accidentally messy for a bit 😬

@shnewto
Copy link
Contributor Author

shnewto commented May 23, 2018

Okay, it looks like we've got two commits now. Thanks again!

@mati865
Copy link
Contributor

mati865 commented Jun 6, 2018

Ping @Manishearth, I assume you want to complete the review?

@shnewto
Copy link
Contributor Author

shnewto commented Jun 12, 2018

@Manishearth or any other reviewers, I'm trying to keep up and resolve conflicts with the master branch as they arise! If I get behind though and you're waiting for conflicts to be resolved before reviewing let me know and I'll address them as soon as I can!

@flip1995
Copy link
Member

Sorry for the long wait time, I'll get to this tomorrow!

Copy link
Member

@flip1995 flip1995 left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution and welcome to clippy! A little bit of nitpicking and some things you need to change will follow: 😉

Sorry for the long wait time again!

}

#[derive(Copy, Clone)]
pub struct IndexingSlicingPass;
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 prefer just IndexingSlicing as the struct name, to keep it consistent with the rest of the code base.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed.

// on the `range` returned by `higher::range(cx, b)`.
// ExprStruct handles &x[n..m], &x[n..] and &x[..n].
// ExprPath handles &x[..] and x[var]
ExprStruct(_, _, _) | ExprPath(_) => {
Copy link
Member

Choose a reason for hiding this comment

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

You can just use ExprStruct(..) (and ExprPath(..)) here.


impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IndexingSlicingPass {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
if let ExprIndex(ref a, ref b) = &expr.node {
Copy link
Member

Choose a reason for hiding this comment

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

Why change the names of the variables from telling names array + index to just a + b?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤦‍♂️

}
match (range.start, range.end) {
(None, Some(_)) => {
cx.span_lint(
Copy link
Member

Choose a reason for hiding this comment

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

Please use the utils::span_lint() methods here. These methods add the link to the clippy documentation to the warning message.

There are also methods for help messages in the utils module. You could use utils::span_help_and_lint() for the Consider using... part of the lint.
Bonus points if you use utils::span_lint_and_sugg() (Can be automatically fixed by rustfix) and generate the right code suggestion. For example: For x[..3] the sugg argument should be x.get(..3). To do this you have to first figure out, whether get() or get_mut() is the right function to use and after that extract the span of the range to insert it as the argument for get[_mut](). Something like this: format!("{}.{}({})", arr/vec_name, "get[_mut]", range.span.as_str()).

But this is definitely not necessary and can be done in a later PR. For now utils::span_help_and_lint() is enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is great to know. Thank you for pointing me in the right direction.

The bonus points suggestion sounds like an awesome value. If it's okay I'll plan on working toward that end in a follow up PR. I'd be happy to raise an issue for that.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds great, thanks!

expr.span,
"slicing may panic. Consider using \
`.get(n..m)` or `.get_mut(n..m)` instead",
);
Copy link
Member

Choose a reason for hiding this comment

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

These calls to span[_help_and]_lint() only differ in the help message. Could you refactor these?

// Else index is in bounds, ok.
}
} else {
cx.span_lint(
Copy link
Member

Choose a reason for hiding this comment

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

Also utils::span_help_and_lint()

(None, None) => (),
}
} else {
cx.span_lint(
Copy link
Member

Choose a reason for hiding this comment

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

Also utils::span_help_and_lint()

@@ -461,6 +459,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
enum_variants::PUB_ENUM_VARIANT_NAMES,
enum_variants::STUTTER,
if_not_else::IF_NOT_ELSE,
indexing_slicing::INDEXING_SLICING,
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be in clippy_restriction. It is at least defined as a restriction lint in indexing_slicing.rs:62.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd be happy to correctly make it a restriction but the request in issue #2536 was that this would be a pedantic lint so for now I'll just fix the declaration.

--> $DIR/indexing_slicing.rs:23:6
|
23 | &x[0..][..3];
| ^^^^^^^^^^^
Copy link
Member

Choose a reason for hiding this comment

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

What happens if you add &x[0..].get(..3) as another test case. Does the lint get triggered on x[0..]?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The lint does not get triggered! It can be added to the test cases.

@shnewto
Copy link
Contributor Author

shnewto commented Jun 13, 2018

@flip1995 thanks for the awesome feedback! I've taken a stab at addressing your requests in the last commit.

(Some(_), Some(_)) => {
help_msg =
"Consider using `.get(n..m)` or `.get_mut(n..m)` instead";
}
Copy link
Member

@flip1995 flip1995 Jun 14, 2018

Choose a reason for hiding this comment

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

You could prettify this a little bit more: see playground

OUT_OF_BOUNDS_INDEXING,
expr.span,
"range is out of bounds",
);
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 suggest to return after linting for an out of bounds range. On the one hand saying that the range is out of bounds here and then that it might panic with the INDEXING_SLICING lint is kind of redundant. On the other hand linting only once would be more consistent with the code below in the indexing [n] case.

// ExprPath handles &x[..] and x[var]
ExprStruct(..) | ExprPath(..) => {
if let Some(range) = higher::range(cx, index) {
let ty = cx.tables.expr_ty(array);
Copy link
Member

Choose a reason for hiding this comment

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

You need the ty in both match cases and could move this in front of the match block.

&x[0..3];
&x[0..][..3];
&x[0..].get(..3); // Ok
Copy link
Member

@flip1995 flip1995 Jun 14, 2018

Choose a reason for hiding this comment

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

Wait this should trigger the INDEXING_SLICING lint too (on x[0..]). Or am I missing something? What happens if you do something like &x[5..].iter().map(|x| 2 * x).collect::<Vec<i32>>()? This should definitely trigger the lint. Could you add this test with a range 5.. (panic) and a range 2.. (no panic). The former case got linted before the change. playground

Copy link
Contributor Author

@shnewto shnewto Jun 14, 2018

Choose a reason for hiding this comment

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

Oh sorry for the confusion! &x[0..].get(..3); does not trigger the lint and was put in this test file to ensure that stderr was not generated for it. And I will add the examples you mentioned 👍

Copy link
Member

Choose a reason for hiding this comment

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

Ahhh I misunderstood this then, thanks for the clarification!


/// **What it does:** Checks for usage of indexing or slicing. Does not report
/// if we can tell that the indexing or slicing operations on an array are in
/// bounds.
Copy link
Member

Choose a reason for hiding this comment

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

This is not really true (yet). In the examples below x[2] is listed as a bad example, but we can definitely tell, that 2 is in bounds. Maybe rewrite the second sentence as a problem and reformulate the "What it does"-part so it states, that get[_mut]() should be used instead of indexing/slicing with [a..b]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There should definitely be an example that shows an array so this statement is clearer. It should be true though that this lint does not report on an array if we can tell that the operation is in bounds.

@shnewto
Copy link
Contributor Author

shnewto commented Jun 14, 2018

@flip1995 thanks for another round of thoughtful feedback. I've made a new pass and have hopefully incorporated or addressed each of this round's changes.

@flip1995
Copy link
Member

Thanks for the quick fixes, I'm currently on the train and have no internet on my laptop, I'll try to take another look at this, this evening or tomorrow!

| ^^^^^^^^
|
= help: Consider using `.get(..n)`or `.get_mut(..n)` instead

error: aborting due to 36 previous errors
error: aborting due to 28 previous errors
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The errors should have gone to 37... Looks like something got bumped in the return refactoring. I'm investigating now.

Copy link
Member

Choose a reason for hiding this comment

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

37? That would be 1 more error message. I think 28 could be right. Before the last commit multiple lines triggered both the INDEXING_SLICING and the OUT_OF_BOUNDS_INDEXING Lint. Now there should only be the OUT_OF_BOUNDS_INDEXING Lint in this cases.

But I didn't really look at the .stderr file yet.

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 totally. You're right about the number going down.

I'm not certain it's still catching everything it should though. Minimally I'm going to push a change that makes that test file a little clearer about what should and shouldn't produce stderr so it's not so easy to get lost.

@shnewto
Copy link
Contributor Author

shnewto commented Jun 14, 2018

@flip1995 Turned out that some cases had slipped through the cracks and were being handled incorrectly. I've pushed a commit that contains a minor linting re-factor and added some comments that will hopefully clear up what output can be expected from this lint's tests.

Thanks again for taking the time with me on this.

@flip1995
Copy link
Member

flip1995 commented Jun 15, 2018

The last commit simplified the lint logic pretty much - nice. Number of errors and error messages seem to be right. LGTM now.

Could you add one more test for indexing with a const instead of a literal:

const N: usize = 15;
x[N];

I'm pretty sure this works just fine, but better save than sorry.

Thanks again for taking the time with me on this.

Thanks for contributing, we're happy about every new and future contributor!

@shnewto
Copy link
Contributor Author

shnewto commented Jun 15, 2018

Always happy to extend tests!

@shnewto
Copy link
Contributor Author

shnewto commented Jun 15, 2018

@flip1995 It looks like there's a CONST_ERR lint in librustc (declared here https://github.com/rust-lang/rust/blob/5205ae8bdc2991eecf3bfbb58ed8f56c0673e738/src/librustc/lint/builtin.rs#L29) that seems to already handle out of bound indexing (https://github.com/rust-lang/rust/blob/5205ae8bdc2991eecf3bfbb58ed8f56c0673e738/src/librustc_mir/transform/const_prop.rs#L545) and the OUT_OF_BOUNDS_INDEXING lint that lives on in this PR steps on it.

If it sounds reasonable to you I'd like to verify that the CONST_ERR lint does catch each of the appropriate cases in this PR's tests then remove the OUT_OF_BOUNDS_INDEXING lint so in order to defer to rustc.

@shnewto
Copy link
Contributor Author

shnewto commented Jun 15, 2018

🤔

rustc's const_err lint does not warn on out of bounds ranged indexing operations on arrays the way this project's OUT_OF_BOUNDS_INDEXING lint does. I'm up for suggestions here, maybe maintain this PR's ranged OUT_OF_BOUNDS_INDEXING lint and remove the other here https://github.com/shnewto/rust-clippy/blob/ce5076b7b852b2d908ef705aee135e09ef10cc95/clippy_lints/src/indexing_slicing.rs#L140?

@flip1995
Copy link
Member

Retriggering travis

@flip1995 flip1995 closed this Jun 18, 2018
@flip1995 flip1995 reopened this Jun 18, 2018
@mati865
Copy link
Contributor

mati865 commented Jun 18, 2018

It does need rebase to build with latest nightly.

@flip1995
Copy link
Member

If it sounds reasonable to you I'd like to verify that the CONST_ERR lint does catch each of the appropriate cases in this PR's tests then remove the OUT_OF_BOUNDS_INDEXING lint so in order to defer to rustc.

I think keeping the OUT_OF_BOUNDS_INDEXING lint as it is, is ok for now. At least as long as we are not on the level of #2536 (comment). If this lint achieves this level, we can think about uplifting it to rustc / integrating it with the const propagator. But since I'm not 100% sure: What do you think about this @oli-obk? Also const_err gets triggered only on nightly.

Maybe related #2846, but we are far from including this lint into this list.

@mati865 it got successfully build with the latest nightly. travis:

$ rustc --version
rustc 1.28.0-nightly (86a8f1a63 2018-06-17)
$ rustup --version
rustup 1.11.0 (e751ff9f8 2018-02-13)
$ cargo --version
cargo 1.28.0-nightly (e2348c2db 2018-06-07)

Everything LGTM now and I would merge this after the CONST_ERR vs. OUT_OF_BOUNDS_INDEXING case is clear.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 18, 2018

I think it's ok to have overlap with the const_err lint for now.

The const_err lint will only lint usize indexing on arrays for quite some time. This PRs lint also triggers on slicing via ranges, maybe it should just not lint for usize indexing on arrays?

@mati865
Copy link
Contributor

mati865 commented Jun 18, 2018

@flip1995 looks like it got auto-rebased:

    Checking clippy_lints v0.0.208 (file:///home/travis/build/rust-lang-nursery/rust-clippy/clippy_lints)
    Checking clippy v0.0.208 (file:///home/travis/build/rust-lang-nursery/rust-clippy)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 05s

    Hey there clippy team! I've made some assumptions in this PR and I'm not at all certain they'll look like the right approach to you. I'm looking forward to any feedback or revision requests you have, thanks!

    Prior to this commit the `indexing_slicing` lint was limited to indexing/slicing operations on arrays. This meant that the scope of a really useful lint didn't include vectors. In order to include vectors in the `indexing_slicing` lint a few steps were taken.

    The `array_indexing.rs` source file in `clippy_lints` was renamed to `indexing_slicing.rs` to more accurately reflect the lint's new scope. The `OUT_OF_BOUNDS_INDEXING` lint persists through these changes so if we can know that a constant index or slice on an array is in bounds no lint is triggered.

    The `array_indexing` tests in the `tests/ui` directory were also extended and moved to `indexing_slicing.rs` and `indexing_slicing.stderr`.

    The `indexing_slicing` lint was moved to the `clippy_pedantic` lint group.

    A specific "Consider using" string was added to each of the `indexing_slicing` lint reports.

    At least one of the test scenarios might look peculiar and I'll leave it up to y'all to decide if it's palatable. It's the result of indexing the array `x` after `let x = [1, 2, 3, 4];`

    ```
    error: slicing may panic. Consider using `.get(..n)`or `.get_mut(..n)`instead
      --> $DIR/indexing_slicing.rs:23:6
       |
    23 |     &x[0..][..3];
       |      ^^^^^^^^^^^
    ```

    The error string reports only on the second half's range-to, because the range-from is in bounds!

    Again, thanks for taking a look.

    Closes #2536
shnewto added 5 commits June 19, 2018 16:27
This commit renames instances of `array_indexing` to `indexing_slicing` and moves the `indexing_slicing` lint to the `clippy_pedantic` group. The justification for this commit's changes are detailed in the previous commit's message.
… process of reviewing PR #2790.

The changes reflected in this commit are as follows:

- Revised `IndexingSlicingPass` struct name to IndexingSlicing for consistency with the rest of the code base.
- Revised match arm condition to use `(..)` shorthand in favor of `(_, _, _)`.
- Restored a couple telling variable names.
- Calls to `cx.span_lint` were revised to use `utils::span_help_and_lint`.
- Took a stab at refactoring some generalizable calls to `utils::span_help_and_lint` to minimize duplicate code.
- Revised INDEXING_SLICING declaration to pedantic rather than restriction.
- Added `&x[0..].get(..3)` to the test cases.
The changes reflected in this commit (requested in PR #2790) are as follows:

- Extended `INDEXING_SLICING` documentation to include the array type so that it is clearer when indexing operations are allowed.
- Variable `ty` defined identically in multiple scopes was moved to an outer scope so it's only defined once.
- Added a missing return statement to ensure only one lint is triggered by a scenario.
- Prettified match statement with a `let` clause. (I learned something new!)
- Added `&x[5..].iter().map(|x| 2 * x).collect::<Vec<i32>>()` and `&x[2..].iter().map(|x| 2 * x).collect::<Vec<i32>>()` to the test cases. The first _should trigger the lint/stderr_ and the second _should not_.
This commit contains a few changes. In an attempt to clarify which test cases should and should not produce stderr it became clear that some cases were being handled incorrectly. In order to address these test cases, a minor re-factor was made to the linting logic itself.

The re-factor was driven by edge case handling including a need for additional match conditions for `ExprCall` (`&x[0..=4]`) and `ExprBinary` (`x[1 << 3]`). Rather than attempt to account for each potential `Expr*` the code was re-factored into simply "if ranged index" and an "otherwise" conditions.
In this commit tests were added to ensure that tests with a `const` index behaved as expected.
In order to minimize the changes to the test's corresponding `stderr`, the tests were appended to
the end of the file.
@shnewto
Copy link
Contributor Author

shnewto commented Jun 19, 2018

Just checking in. Is anyone currently expecting action on my end?

@flip1995
Copy link
Member

maybe it should just not lint for usize indexing on arrays?

It would be great if you could address this.

This commit removes the logic in this PR that linted out-of-bounds constant `usize` indexing on arrays. That case is already handled by rustc's `const_err` lint. Beyond removing the linting logic, the test file and its associated stderr were updated to verify that const `usize` indexing operations on arrays are no longer handled by this `indexing_slicing` lint.
@shnewto
Copy link
Contributor Author

shnewto commented Jun 20, 2018

I went ahead and removed the lint for constant usize indexing on arrays. The test code that uses constant usize indexing was left in (and the stderr modified) to ensure that the indexing_slicing lint is no longer triggered in those cases.

@oli-obk oli-obk merged commit 25510cf into rust-lang:master Jun 21, 2018
@oli-obk
Copy link
Contributor

oli-obk commented Jun 21, 2018

Thanks for bearing with us!

@shnewto shnewto deleted the vectors-to-indexing-slicing-lint branch June 21, 2018 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants