Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6472,6 +6472,7 @@ Released 2018-09-13
[`for_loop_over_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_option
[`for_loop_over_result`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loop_over_result
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
[`for_unbounded_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_unbounded_range
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
[`forget_non_drop`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_non_drop
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::loops::EXPLICIT_INTO_ITER_LOOP_INFO,
crate::loops::EXPLICIT_ITER_LOOP_INFO,
crate::loops::FOR_KV_MAP_INFO,
crate::loops::FOR_UNBOUNDED_RANGE_INFO,
crate::loops::INFINITE_LOOP_INFO,
crate::loops::ITER_NEXT_LOOP_INFO,
crate::loops::MANUAL_FIND_INFO,
Expand Down
33 changes: 33 additions & 0 deletions clippy_lints/src/loops/for_unbounded_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use super::FOR_UNBOUNDED_RANGE;
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::higher;
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_span::Span;

pub fn check<'tcx>(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>, span: Span) {
if let Some(range) = higher::Range::hir(cx, arg)
&& let Some(range_start) = range.start
&& let None = range.end
&& let ty = cx.typeck_results().expr_ty_adjusted(range_start)
&& (ty.is_integral() || ty.is_char())
{
let until_max = format!("={ty}::MAX");

span_lint_hir_and_then(
cx,
FOR_UNBOUNDED_RANGE,
arg.hir_id,
span,
"for loop on unbounded range (`0..`)",
|diag| {
diag.span_suggestion_verbose(
arg.span.shrink_to_hi(),
"for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead",
until_max,
rustc_errors::Applicability::MachineApplicable,
);
},
);
}
}
32 changes: 32 additions & 0 deletions clippy_lints/src/loops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod explicit_counter_loop;
mod explicit_into_iter_loop;
mod explicit_iter_loop;
mod for_kv_map;
mod for_unbounded_range;
mod infinite_loop;
mod iter_next_loop;
mod manual_find;
Expand Down Expand Up @@ -785,6 +786,35 @@ declare_clippy_lint! {
"using the character position yielded by `.chars().enumerate()` in a context where a byte index is expected"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for unbounded for loops over char or integers.
///
/// ### Why is this bad?
/// Using a unbounded range over char and integers will unexpectedly not handle overflows so it will lead to panics
/// or infinite loops.
///
/// Instead there should be a max value set, usually the `MAX` constant for a given type such as `'\0'..char::MAX`
/// or `250..u8::MAX`.
///
/// ### Example
/// ```no_run
/// for i in 250u8.. {
/// println!("{i}");
/// }
/// ```
/// Use instead:
/// ```no_run
/// for i in 250u8..=u8::MAX {
/// println!("{i}");
/// }
/// ```
#[clippy::version = "1.94.0"]
pub FOR_UNBOUNDED_RANGE,
nursery,
"using a for loop over unbounded range (`0..`)"
}

pub struct Loops {
msrv: Msrv,
enforce_iter_loop_reborrow: bool,
Expand Down Expand Up @@ -823,6 +853,7 @@ impl_lint_pass!(Loops => [
INFINITE_LOOP,
MANUAL_SLICE_FILL,
CHAR_INDICES_AS_BYTE_INDICES,
FOR_UNBOUNDED_RANGE,
]);

impl<'tcx> LateLintPass<'tcx> for Loops {
Expand Down Expand Up @@ -951,6 +982,7 @@ impl Loops {
manual_find::check(cx, pat, arg, body, span, expr);
unused_enumerate_index::check(cx, arg, pat, None, body);
char_indices_as_byte_indices::check(cx, pat, arg, body);
for_unbounded_range::check(cx, arg, span);
}

fn check_for_loop_arg(&self, cx: &LateContext<'_>, _: &Pat<'_>, arg: &Expr<'_>) {
Expand Down
19 changes: 19 additions & 0 deletions tests/ui/for_unbounded_range.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::for_unbounded_range)]

fn do_something<T: Copy>(_t: T) {}

fn main() {
for i in 0_u8..=u8::MAX {
do_something(i);
}

for i in 0_u8..=u8::MAX {
//~^ for_unbounded_range
do_something(i);
}

for i in '\0'..=char::MAX {
//~^ for_unbounded_range
do_something(i);
}
}
19 changes: 19 additions & 0 deletions tests/ui/for_unbounded_range.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#![warn(clippy::for_unbounded_range)]

fn do_something<T: Copy>(_t: T) {}

fn main() {
for i in 0_u8..=u8::MAX {
do_something(i);
}

for i in 0_u8.. {
//~^ for_unbounded_range
do_something(i);
}

for i in '\0'.. {
//~^ for_unbounded_range
do_something(i);
}
}
32 changes: 32 additions & 0 deletions tests/ui/for_unbounded_range.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: for loop on unbounded range (`0..`)
--> tests/ui/for_unbounded_range.rs:10:5
|
LL | / for i in 0_u8.. {
LL | |
LL | | do_something(i);
LL | | }
| |_____^
|
= note: `-D clippy::for-unbounded-range` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::for_unbounded_range)]`
help: for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead
|
LL | for i in 0_u8..=u8::MAX {
| ++++++++

error: for loop on unbounded range (`0..`)
--> tests/ui/for_unbounded_range.rs:15:5
|
LL | / for i in '\0'.. {
LL | |
LL | | do_something(i);
LL | | }
| |_____^
|
help: for loops over unbounded ranges will wrap around, consider using `start..=MAX` instead
|
LL | for i in '\0'..=char::MAX {
| ++++++++++

error: aborting due to 2 previous errors