fix missing_asserts_for_indexing ignores match for length - #17400
fix missing_asserts_for_indexing ignores match for length#17400wasd243 wants to merge 5 commits into
missing_asserts_for_indexing ignores match for length#17400Conversation
…arantees the index is in bounds
|
Thanks for the pull request, and welcome!You should hear from one of our reviewers after this PR is reviewed by at least 2 reviewers from the community Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (
|
|
Lintcheck changes for cd5f376
This comment will be updated if you push new changes |
| && is_slice_len_expr(cx, scrutinee, slice) | ||
| && match_arms_bound_len(cx, index_expr, arms) | ||
| { | ||
| return arms.iter().any(|arm| is_wild(arm.pat)); |
There was a problem hiding this comment.
This assumes that if we have a wildcard anywhere, then the entire match is safe, even if we are accessing indexes we can't safely assume.
fn foo(supported: &[u8]) {
match supported.len() {
0 => {},
1 => {},
2 => println!("{} {}", supported[0], supported[2]),
_ => println!("{} {}", supported[0], supported[2]),
}
}There was a problem hiding this comment.
Fixed -- literal arms now require index < n (the arm pins len to exactly n), instead of the shared index <= max check.
Lints now:
match supported.len() {
0 => {},
1 => {},
2 => println!("{} {}", supported[2], supported[3]), // both out of bounds for len == 2 → lints
_ => {},
}The example doesn't lint but for a pre-existing reason unrelated to the suppression:
match supported.len() {
0 => {},
1 => {},
2 => println!("{} {}", supported[0], supported[2]), // [0] in bounds → suppressed; [2] NOT suppressed
_ => println!("{} {}", supported[0], supported[2]), // both in bounds (len >= 3) → suppressed
}Co-authored-by: Gri-ffin <82527700+Gri-ffin@users.noreply.github.com>
| fn fix_match_case(supported: &[u8]) { | ||
| match supported.len() { | ||
| 0 => {}, | ||
| 1 => println!("{}", supported[0]), | ||
| _ => println!("{} or {}", supported[0], supported[1]), | ||
| } |
There was a problem hiding this comment.
I think this lint suggestion can be tightened by using the structural pattern matching on slices.
| fn fix_match_case(supported: &[u8]) { | |
| match supported.len() { | |
| 0 => {}, | |
| 1 => println!("{}", supported[0]), | |
| _ => println!("{} or {}", supported[0], supported[1]), | |
| } | |
| fn fix_match_case(supported: &[u8]) { | |
| match supported { | |
| [] => {}, | |
| [first] => println!("{}", first), | |
| [first, second, ...] => println!("{} or {}", first, second), | |
| } |
Much more work though and might be better in another lint.
There was a problem hiding this comment.
@CommanderStorm pattern matching is not always available: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0afb61b6183cf5bd030edea69c8a52e9
There was a problem hiding this comment.
Yes, you neee to typecheck that it is an slice, if the alternative suggestion makes sense.
69029e4 to
cd5f376
Compare
|
☔ The latest upstream changes (possibly #17607) made this pull request unmergeable. Please resolve the merge conflicts. |
|
This PR may be out of scope. It's difficult to reliably reason about potential panics in Although the issue is a valid false positive, addressing it more generally would conflict with the current scope and behavior of the lint, and some of the related work would likely be better suited for a separate lint. I think it would be better to close this PR temporarily. |
|
While I agree on generalization issue, is there a possibility to cover at least some cases. I don't even ask about suggestions to replace with, which can land in a documentation rather than be automatic replacements. |
Can we boil this down to very simple patterns where the lint is save? Is there something that we can learn from your effort for this different lint? |
Maybe we could, but if we only use HIR's patterns and hardcode each specific cases, it would be complex to review the code and fix other cases FP/FN. Even if we only treat this as a false-positive case, the amount of code would be difficult to keep under control. I tried to handle this as a narrow false positive in this PR, but it's 100-200 lines added only for a fn foo(supported: &[u8]) {
match supported.len() {
0 => {},
1 => {},
2 => println!("{} {}", supported[0], supported[2]),
_ => println!("{} {}", supported[0], supported[2]),
}
}and y21 already suggested on #17399 an even better solution:
In
This kind of false positive is already recorded in the lint's docs as a known limitation, and the docs explicitly note that these cases can be difficult to reason about. For this, my personal answer is no; it may be out of scope. Is the suggestion maybe incorrect?Yes. I tried this code, the same reproducer in issue #17398 fn issue17398(supported: &[u8]) {
match supported.len() { // `match` without assert
0 => {},
1 => println!("{}", supported[0]),
_ => println!("{} or {}", supported[0], supported[1]),
//~^^ missing_asserts_for_indexing
}
}In this false-positive case, rustfix does not apply the suggestion to the Is the current value being If not, it would be better to change it into |
|
It's worth a while to at least list all false positives you've met and struggled with, so there would be an extensive test scenarios for the future. This also would provide an overview, how problematic would be an implementation for general cases. As for the fix, without knowledge o an exact type (e.g must be a slice for pattern matching for elements), it's impossible to suggest any fix, but it's worth a while to mention some in documentation, otherwise, this assert could become an annoyance. |
Fixes #17398
missing_asserts_for_indexingpreviously ignoredmatchexpressions, so indexing inside amatchon the slice's length was linted even when the match arms already guarantee the indices are in bounds. For example:Here the
0and1arms rule out all lengths below 2, so the indexing in the wildcard arm cannot fail and the bounds checks are already elided without anassert!.This PR suppresses the lint for an index inside a
matchonslice.len()when the non-wildcard arms are integer literals contiguously covering0..=max(so the wildcard arm implieslen > max) and the index is at mostmax.The check is deliberately conservative and falls back to linting as before when:
0..=max(e.g.5 => .., _ => .., where_can still be0)0 | 1 => ..is safe in principle but not analyzed)One behavioral note: suppression is per-index. If a
matchguaranteeslen > 1but the wildcard arm indexes[0],[1]and[2], only[2]remains unsuppressed, which is equivalent to a single unchecked access and therefore doesn't lint (consistent with the lint never firing on single accesses).changelog: [
missing_asserts_for_indexing]: fix false positive when amatchon the slice length guarantees the indices are in bounds