Skip to content

Commit cd100aa

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

File tree

7 files changed

+142
-0
lines changed

7 files changed

+142
-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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
dbg!();
25+
diag.span_suggestion_verbose(
26+
arg.span.shrink_to_hi(),
27+
"for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead",
28+
until_max,
29+
rustc_errors::Applicability::MachineApplicable,
30+
);
31+
},
32+
);
33+
}
34+
}

clippy_lints/src/loops/mod.rs

Lines changed: 34 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,37 @@ 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+
///
792+
/// Check for unbounded for loops over char or integers.
793+
///
794+
/// ### Why is this bad?
795+
///
796+
/// Using a unbounded range over char and integers will unexpectedly not handle overflows so it will lead to panics
797+
/// or infinite loops.
798+
///
799+
/// Instead there should be a max value set, usually the `MAX` constant for a given type such as `'\0'..char::MAX`
800+
/// or `250..u8::MAX`.
801+
///
802+
/// ### Example
803+
/// ```no_run
804+
/// for i in 250u8.. {
805+
/// println!("{i}");
806+
/// }
807+
/// ```
808+
/// Use instead:
809+
/// ```no_run
810+
/// for i in 250u8..=u8::MAX {
811+
/// println!("{i}");
812+
/// }
813+
/// ```
814+
#[clippy::version = "1.94.0"]
815+
pub FOR_UNBOUNDED_RANGE,
816+
nursery,
817+
"for loop on unbounded range (`0..`)"
818+
}
819+
788820
pub struct Loops {
789821
msrv: Msrv,
790822
enforce_iter_loop_reborrow: bool,
@@ -823,6 +855,7 @@ impl_lint_pass!(Loops => [
823855
INFINITE_LOOP,
824856
MANUAL_SLICE_FILL,
825857
CHAR_INDICES_AS_BYTE_INDICES,
858+
FOR_UNBOUNDED_RANGE,
826859
]);
827860

828861
impl<'tcx> LateLintPass<'tcx> for Loops {
@@ -951,6 +984,7 @@ impl Loops {
951984
manual_find::check(cx, pat, arg, body, span, expr);
952985
unused_enumerate_index::check(cx, arg, pat, None, body);
953986
char_indices_as_byte_indices::check(cx, pat, arg, body);
987+
for_unbounded_range::check(cx, arg, span)
954988
}
955989

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

0 commit comments

Comments
 (0)