Skip to content

Commit 9220570

Browse files
committed
add new lint: for_unbounded_range
1 parent 37330eb commit 9220570

File tree

7 files changed

+137
-0
lines changed

7 files changed

+137
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6472,6 +6472,7 @@ Released 2018-09-13
64726472
[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option
64736473
[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
64746474
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
6475+
[`for_unbounded_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_unbounded_range
64756476
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
64766477
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
64776478
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
271271
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
272272
crate::loops::EXPLICIT_ITER_LOOP_INFO,
273273
crate::loops::FOR_KV_MAP_INFO,
274+
crate::loops::FOR_UNBOUNDED_RANGE_INFO,
274275
crate::loops::INFINITE_LOOP_INFO,
275276
crate::loops::ITER_NEXT_LOOP_INFO,
276277
crate::loops::MANUAL_FIND_INFO,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use super::FOR_UNBOUNDED_RANGE;
2+
use clippy_utils::diagnostics::span_lint_hir_and_then;
3+
use clippy_utils::higher;
4+
use rustc_hir::Expr;
5+
use rustc_lint::LateContext;
6+
use rustc_span::Span;
7+
8+
pub fn check<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>, span: Span) {
9+
if let Some(range) = higher::Range::hir(cx, arg)
10+
&& let Some(range_start) = range.start
11+
&& let None = range.end
12+
&& let ty = cx.typeck_results().expr_ty_adjusted(range_start)
13+
&& (ty.is_integral() || ty.is_char())
14+
{
15+
let until_max = format!("={ty}::MAX");
16+
17+
span_lint_hir_and_then(
18+
cx,
19+
FOR_UNBOUNDED_RANGE,
20+
arg.hir_id,
21+
span,
22+
"for loop on unbounded range (`0..`)",
23+
|diag| {
24+
diag.span_suggestion_verbose(
25+
arg.span.shrink_to_hi(),
26+
"for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead",
27+
until_max,
28+
rustc_errors::Applicability::MachineApplicable,
29+
);
30+
},
31+
);
32+
}
33+
}

clippy_lints/src/loops/mod.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod explicit_counter_loop;
44
mod explicit_into_iter_loop;
55
mod explicit_iter_loop;
66
mod for_kv_map;
7+
mod for_unbounded_range;
78
mod infinite_loop;
89
mod iter_next_loop;
910
mod manual_find;
@@ -785,6 +786,35 @@ declare_clippy_lint! {
785786
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"
786787
}
787788

789+
declare_clippy_lint! {
790+
/// ### What it does
791+
/// Checks for unbounded for loops over char or integers.
792+
///
793+
/// ### Why is this bad?
794+
/// Using a unbounded range over char and integers will unexpectedly not handle overflows so it will lead to panics
795+
/// or infinite loops.
796+
///
797+
/// Instead there should be a max value set, usually the `MAX` constant for a given type such as `'\0'..char::MAX`
798+
/// or `250..u8::MAX`.
799+
///
800+
/// ### Example
801+
/// ```no_run
802+
/// for i in 250u8.. {
803+
/// println!("{i}");
804+
/// }
805+
/// ```
806+
/// Use instead:
807+
/// ```no_run
808+
/// for i in 250u8..=u8::MAX {
809+
/// println!("{i}");
810+
/// }
811+
/// ```
812+
#[clippy::version = "1.94.0"]
813+
pub FOR_UNBOUNDED_RANGE,
814+
nursery,
815+
"using a for loop over unbounded range (`0..`)"
816+
}
817+
788818
pub struct Loops {
789819
msrv: Msrv,
790820
enforce_iter_loop_reborrow: bool,
@@ -823,6 +853,7 @@ impl_lint_pass!(Loops => [
823853
INFINITE_LOOP,
824854
MANUAL_SLICE_FILL,
825855
CHAR_INDICES_AS_BYTE_INDICES,
856+
FOR_UNBOUNDED_RANGE,
826857
]);
827858

828859
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -951,6 +982,7 @@ impl Loops {
951982
manual_find::check(cx, pat, arg, body, span, expr);
952983
unused_enumerate_index::check(cx, arg, pat, None, body);
953984
char_indices_as_byte_indices::check(cx, pat, arg, body);
985+
for_unbounded_range::check(cx, arg, span);
954986
}
955987

956988
fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {

tests/ui/for_unbounded_range.fixed

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![warn(clippy::for_unbounded_range)]
2+
3+
fn do_something<T: Copy>(_t: T) {}
4+
5+
fn main() {
6+
for i in 0_u8..=u8::MAX {
7+
do_something(i);
8+
}
9+
10+
for i in 0_u8..=u8::MAX {
11+
//~^ for_unbounded_range
12+
do_something(i);
13+
}
14+
15+
for i in '\0'..=char::MAX {
16+
//~^ for_unbounded_range
17+
do_something(i);
18+
}
19+
}

tests/ui/for_unbounded_range.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#![warn(clippy::for_unbounded_range)]
2+
3+
fn do_something<T: Copy>(_t: T) {}
4+
5+
fn main() {
6+
for i in 0_u8..=u8::MAX {
7+
do_something(i);
8+
}
9+
10+
for i in 0_u8.. {
11+
//~^ for_unbounded_range
12+
do_something(i);
13+
}
14+
15+
for i in '\0'.. {
16+
//~^ for_unbounded_range
17+
do_something(i);
18+
}
19+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: for loop on unbounded range (`0..`)
2+
--> tests/ui/for_unbounded_range.rs:10:5
3+
|
4+
LL | / for i in 0_u8.. {
5+
LL | |
6+
LL | | do_something(i);
7+
LL | | }
8+
| |_____^
9+
|
10+
= note: `-D clippy::for-unbounded-range` implied by `-D warnings`
11+
= help: to override `-D warnings` add `#[allow(clippy::for_unbounded_range)]`
12+
help: for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead
13+
|
14+
LL | for i in 0_u8..=u8::MAX {
15+
| ++++++++
16+
17+
error: for loop on unbounded range (`0..`)
18+
--> tests/ui/for_unbounded_range.rs:15:5
19+
|
20+
LL | / for i in '\0'.. {
21+
LL | |
22+
LL | | do_something(i);
23+
LL | | }
24+
| |_____^
25+
|
26+
help: for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead
27+
|
28+
LL | for i in '\0'..=char::MAX {
29+
| ++++++++++
30+
31+
error: aborting due to 2 previous errors
32+

0 commit comments

Comments
 (0)