Skip to content

Commit 459897b

Browse files
authored
missing_asserts_for_indexing: consider assert_eq!() as well (#14258)
`assert_eq!()` and `assert_ne!()` are not expanded the same way as `assert!()` (they use a `match` instead of a `if`). This makes them being recognized as well. Fix rust-lang/rust-clippy#14255 changelog: [`missing_asserts_for_indexing`]: consider `assert_eq!()` as well
2 parents d3267e9 + d1c315a commit 459897b

6 files changed

+145
-15
lines changed

clippy_lints/src/missing_asserts_for_indexing.rs

+26-13
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ use std::ops::ControlFlow;
33

44
use clippy_utils::comparisons::{Rel, normalize_comparison};
55
use clippy_utils::diagnostics::span_lint_and_then;
6+
use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace};
67
use clippy_utils::source::snippet;
78
use clippy_utils::visitors::for_each_expr_without_closures;
89
use clippy_utils::{eq_expr_value, hash_expr, higher};
9-
use rustc_ast::{LitKind, RangeLimits};
10+
use rustc_ast::{BinOpKind, LitKind, RangeLimits};
1011
use rustc_data_structures::packed::Pu128;
1112
use rustc_data_structures::unhash::UnindexMap;
1213
use rustc_errors::{Applicability, Diag};
13-
use rustc_hir::{BinOp, Block, Body, Expr, ExprKind, UnOp};
14+
use rustc_hir::{Block, Body, Expr, ExprKind, UnOp};
1415
use rustc_lint::{LateContext, LateLintPass};
1516
use rustc_session::declare_lint_pass;
1617
use rustc_span::source_map::Spanned;
@@ -97,7 +98,7 @@ enum LengthComparison {
9798
///
9899
/// E.g. for `v.len() > 5` this returns `Some((LengthComparison::IntLessThanLength, 5, v.len()))`
99100
fn len_comparison<'hir>(
100-
bin_op: BinOp,
101+
bin_op: BinOpKind,
101102
left: &'hir Expr<'hir>,
102103
right: &'hir Expr<'hir>,
103104
) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
@@ -112,7 +113,7 @@ fn len_comparison<'hir>(
112113

113114
// normalize comparison, `v.len() > 4` becomes `4 < v.len()`
114115
// this simplifies the logic a bit
115-
let (op, left, right) = normalize_comparison(bin_op.node, left, right)?;
116+
let (op, left, right) = normalize_comparison(bin_op, left, right)?;
116117
match (op, left.kind, right.kind) {
117118
(Rel::Lt, int_lit_pat!(left), _) => Some((LengthComparison::IntLessThanLength, left as usize, right)),
118119
(Rel::Lt, _, int_lit_pat!(right)) => Some((LengthComparison::LengthLessThanInt, right as usize, left)),
@@ -134,18 +135,30 @@ fn assert_len_expr<'hir>(
134135
cx: &LateContext<'_>,
135136
expr: &'hir Expr<'hir>,
136137
) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> {
137-
if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
138+
let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr)
138139
&& let ExprKind::Unary(UnOp::Not, condition) = &cond.kind
139140
&& let ExprKind::Binary(bin_op, left, right) = &condition.kind
140-
141-
&& let Some((cmp, asserted_len, slice_len)) = len_comparison(*bin_op, left, right)
142-
&& let ExprKind::MethodCall(method, recv, [], _) = &slice_len.kind
143-
&& cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
144-
&& method.ident.name == sym::len
145-
146141
// check if `then` block has a never type expression
147142
&& let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind
148143
&& cx.typeck_results().expr_ty(then_expr).is_never()
144+
{
145+
len_comparison(bin_op.node, left, right)?
146+
} else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| {
147+
match cx.tcx.get_diagnostic_name(macro_call.def_id) {
148+
Some(sym::assert_eq_macro) => Some((macro_call, BinOpKind::Eq)),
149+
Some(sym::assert_ne_macro) => Some((macro_call, BinOpKind::Ne)),
150+
_ => None,
151+
}
152+
}) && let Some((left, right, _)) = find_assert_eq_args(cx, expr, macro_call.expn)
153+
{
154+
len_comparison(bin_op, left, right)?
155+
} else {
156+
return None;
157+
};
158+
159+
if let ExprKind::MethodCall(method, recv, [], _) = &slice_len.kind
160+
&& cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice()
161+
&& method.ident.name == sym::len
149162
{
150163
Some((cmp, asserted_len, recv))
151164
} else {
@@ -310,7 +323,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
310323
indexes: mem::take(indexes),
311324
is_first_highest: *is_first_highest,
312325
slice,
313-
assert_span: expr.span,
326+
assert_span: expr.span.source_callsite(),
314327
comparison,
315328
asserted_len,
316329
};
@@ -319,7 +332,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un
319332
indexes.push(IndexEntry::StrayAssert {
320333
asserted_len,
321334
comparison,
322-
assert_span: expr.span,
335+
assert_span: expr.span.source_callsite(),
323336
slice,
324337
});
325338
}

tests/ui/missing_asserts_for_indexing.fixed

+17
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,21 @@ fn highest_index_first(v1: &[u8]) {
149149
let _ = v1[2] + v1[1] + v1[0];
150150
}
151151

152+
fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) {
153+
assert!(v1.len() == 3);
154+
assert_eq!(v2.len(), 4);
155+
assert!(v3.len() == 3);
156+
assert_eq!(4, v4.len());
157+
158+
let _ = v1[0] + v1[1] + v1[2];
159+
//~^ missing_asserts_for_indexing
160+
161+
let _ = v2[0] + v2[1] + v2[2];
162+
163+
let _ = v3[0] + v3[1] + v3[2];
164+
//~^ missing_asserts_for_indexing
165+
166+
let _ = v4[0] + v4[1] + v4[2];
167+
}
168+
152169
fn main() {}

tests/ui/missing_asserts_for_indexing.rs

+17
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,21 @@ fn highest_index_first(v1: &[u8]) {
149149
let _ = v1[2] + v1[1] + v1[0];
150150
}
151151

152+
fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) {
153+
assert_eq!(v1.len(), 2);
154+
assert_eq!(v2.len(), 4);
155+
assert_eq!(2, v3.len());
156+
assert_eq!(4, v4.len());
157+
158+
let _ = v1[0] + v1[1] + v1[2];
159+
//~^ missing_asserts_for_indexing
160+
161+
let _ = v2[0] + v2[1] + v2[2];
162+
163+
let _ = v3[0] + v3[1] + v3[2];
164+
//~^ missing_asserts_for_indexing
165+
166+
let _ = v4[0] + v4[1] + v4[2];
167+
}
168+
152169
fn main() {}

tests/ui/missing_asserts_for_indexing.stderr

+53-1
Original file line numberDiff line numberDiff line change
@@ -301,5 +301,57 @@ LL | let _ = v3[0] + v3[1] + v3[2];
301301
| ^^^^^
302302
= note: asserting the length before indexing will elide bounds checks
303303

304-
error: aborting due to 11 previous errors
304+
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
305+
--> tests/ui/missing_asserts_for_indexing.rs:158:13
306+
|
307+
LL | assert_eq!(v1.len(), 2);
308+
| ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() == 3)`
309+
...
310+
LL | let _ = v1[0] + v1[1] + v1[2];
311+
| ^^^^^^^^^^^^^^^^^^^^^
312+
|
313+
note: slice indexed here
314+
--> tests/ui/missing_asserts_for_indexing.rs:158:13
315+
|
316+
LL | let _ = v1[0] + v1[1] + v1[2];
317+
| ^^^^^
318+
note: slice indexed here
319+
--> tests/ui/missing_asserts_for_indexing.rs:158:21
320+
|
321+
LL | let _ = v1[0] + v1[1] + v1[2];
322+
| ^^^^^
323+
note: slice indexed here
324+
--> tests/ui/missing_asserts_for_indexing.rs:158:29
325+
|
326+
LL | let _ = v1[0] + v1[1] + v1[2];
327+
| ^^^^^
328+
= note: asserting the length before indexing will elide bounds checks
329+
330+
error: indexing into a slice multiple times with an `assert` that does not cover the highest index
331+
--> tests/ui/missing_asserts_for_indexing.rs:163:13
332+
|
333+
LL | assert_eq!(2, v3.len());
334+
| ----------------------- help: provide the highest index that is indexed with: `assert!(v3.len() == 3)`
335+
...
336+
LL | let _ = v3[0] + v3[1] + v3[2];
337+
| ^^^^^^^^^^^^^^^^^^^^^
338+
|
339+
note: slice indexed here
340+
--> tests/ui/missing_asserts_for_indexing.rs:163:13
341+
|
342+
LL | let _ = v3[0] + v3[1] + v3[2];
343+
| ^^^^^
344+
note: slice indexed here
345+
--> tests/ui/missing_asserts_for_indexing.rs:163:21
346+
|
347+
LL | let _ = v3[0] + v3[1] + v3[2];
348+
| ^^^^^
349+
note: slice indexed here
350+
--> tests/ui/missing_asserts_for_indexing.rs:163:29
351+
|
352+
LL | let _ = v3[0] + v3[1] + v3[2];
353+
| ^^^^^
354+
= note: asserting the length before indexing will elide bounds checks
355+
356+
error: aborting due to 13 previous errors
305357

tests/ui/missing_asserts_for_indexing_unfixable.rs

+7
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,11 @@ fn assert_after_indexing(v1: &[u8]) {
7979
assert!(v1.len() > 2);
8080
}
8181

82+
fn issue14255(v1: &[u8]) {
83+
assert_ne!(v1.len(), 2);
84+
85+
let _ = v1[0] + v1[1] + v1[2];
86+
//~^ missing_asserts_for_indexing
87+
}
88+
8289
fn main() {}

tests/ui/missing_asserts_for_indexing_unfixable.stderr

+25-1
Original file line numberDiff line numberDiff line change
@@ -199,5 +199,29 @@ LL | let _ = v1[1] + v1[2];
199199
| ^^^^^
200200
= note: asserting the length before indexing will elide bounds checks
201201

202-
error: aborting due to 9 previous errors
202+
error: indexing into a slice multiple times without an `assert`
203+
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:13
204+
|
205+
LL | let _ = v1[0] + v1[1] + v1[2];
206+
| ^^^^^^^^^^^^^^^^^^^^^
207+
|
208+
= help: consider asserting the length before indexing: `assert!(v1.len() > 2);`
209+
note: slice indexed here
210+
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:13
211+
|
212+
LL | let _ = v1[0] + v1[1] + v1[2];
213+
| ^^^^^
214+
note: slice indexed here
215+
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:21
216+
|
217+
LL | let _ = v1[0] + v1[1] + v1[2];
218+
| ^^^^^
219+
note: slice indexed here
220+
--> tests/ui/missing_asserts_for_indexing_unfixable.rs:85:29
221+
|
222+
LL | let _ = v1[0] + v1[1] + v1[2];
223+
| ^^^^^
224+
= note: asserting the length before indexing will elide bounds checks
225+
226+
error: aborting due to 10 previous errors
203227

0 commit comments

Comments
 (0)