1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
3 use clippy_utils::{get_trait_def_id, higher, is_qpath_def_path, paths};
4 use rustc_hir::{BorrowKind, Expr, ExprKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_session::{declare_lint_pass, declare_tool_lint};
7 use rustc_span::symbol::{sym, Symbol};
10 /// **What it does:** Checks for iteration that is guaranteed to be infinite.
12 /// **Why is this bad?** While there may be places where this is acceptable
13 /// (e.g., in event streams), in most cases this is simply an error.
15 /// **Known problems:** None.
21 /// iter::repeat(1_u8).collect::<Vec<_>>();
28 declare_clippy_lint! {
29 /// **What it does:** Checks for iteration that may be infinite.
31 /// **Why is this bad?** While there may be places where this is acceptable
32 /// (e.g., in event streams), in most cases this is simply an error.
34 /// **Known problems:** The code may have a condition to stop iteration, but
35 /// this lint is not clever enough to analyze it.
39 /// let infinite_iter = 0..;
40 /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
42 pub MAYBE_INFINITE_ITER,
44 "possible infinite iteration"
47 declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]);
49 impl<'tcx> LateLintPass<'tcx> for InfiniteIter {
50 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
51 let (lint, msg) = match complete_infinite_iter(cx, expr) {
52 Infinite => (INFINITE_ITER, "infinite iteration detected"),
53 MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
58 span_lint(cx, lint, expr.span, msg);
62 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
69 use self::Finiteness::{Finite, Infinite, MaybeInfinite};
73 fn and(self, b: Self) -> Self {
75 (Finite, _) | (_, Finite) => Finite,
76 (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
82 fn or(self, b: Self) -> Self {
84 (Infinite, _) | (_, Infinite) => Infinite,
85 (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
91 impl From<bool> for Finiteness {
93 fn from(b: bool) -> Self {
94 if b { Infinite } else { Finite }
98 /// This tells us what to look for to know if the iterator returned by
99 /// this method is infinite
100 #[derive(Copy, Clone)]
102 /// infinite no matter what
104 /// infinite if the first argument is
106 /// infinite if any of the supplied arguments is
108 /// infinite if all of the supplied arguments are
112 use self::Heuristic::{All, Always, Any, First};
114 /// a slice of (method name, number of args, heuristic, bounds) tuples
115 /// that will be used to determine whether the method in question
116 /// returns an infinite or possibly infinite iterator. The finiteness
117 /// is an upper bound, e.g., some methods can return a possibly
118 /// infinite iterator at worst, e.g., `take_while`.
119 const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
120 ("zip", 2, All, Infinite),
121 ("chain", 2, Any, Infinite),
122 ("cycle", 1, Always, Infinite),
123 ("map", 2, First, Infinite),
124 ("by_ref", 1, First, Infinite),
125 ("cloned", 1, First, Infinite),
126 ("rev", 1, First, Infinite),
127 ("inspect", 1, First, Infinite),
128 ("enumerate", 1, First, Infinite),
129 ("peekable", 2, First, Infinite),
130 ("fuse", 1, First, Infinite),
131 ("skip", 2, First, Infinite),
132 ("skip_while", 1, First, Infinite),
133 ("filter", 2, First, Infinite),
134 ("filter_map", 2, First, Infinite),
135 ("flat_map", 2, First, Infinite),
136 ("unzip", 1, First, Infinite),
137 ("take_while", 2, First, MaybeInfinite),
138 ("scan", 3, First, MaybeInfinite),
141 fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
143 ExprKind::MethodCall(method, _, args, _) => {
144 for &(name, len, heuristic, cap) in &HEURISTICS {
145 if method.ident.name.as_str() == name && args.len() == len {
146 return (match heuristic {
148 First => is_infinite(cx, &args[0]),
149 Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])),
150 All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])),
155 if method.ident.name == sym!(flat_map) && args.len() == 2 {
156 if let ExprKind::Closure(_, _, body_id, _, _) = args[1].kind {
157 let body = cx.tcx.hir().body(body_id);
158 return is_infinite(cx, &body.value);
163 ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
164 ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
165 ExprKind::Call(path, _) => {
166 if let ExprKind::Path(ref qpath) = path.kind {
167 is_qpath_def_path(cx, qpath, path.hir_id, &paths::ITER_REPEAT).into()
172 ExprKind::Struct(..) => higher::range(expr).map_or(false, |r| r.end.is_none()).into(),
177 /// the names and argument lengths of methods that *may* exhaust their
179 const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
188 /// the names and argument lengths of methods that *always* exhaust
190 const COMPLETING_METHODS: [(&str, usize); 12] = [
205 /// the paths of types that are known to be infinitely allocating
206 const INFINITE_COLLECTORS: &[Symbol] = &[
217 fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
219 ExprKind::MethodCall(method, _, args, _) => {
220 for &(name, len) in &COMPLETING_METHODS {
221 if method.ident.name.as_str() == name && args.len() == len {
222 return is_infinite(cx, &args[0]);
225 for &(name, len) in &POSSIBLY_COMPLETING_METHODS {
226 if method.ident.name.as_str() == name && args.len() == len {
227 return MaybeInfinite.and(is_infinite(cx, &args[0]));
230 if method.ident.name == sym!(last) && args.len() == 1 {
231 let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR).map_or(false, |id| {
232 !implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[])
234 if not_double_ended {
235 return is_infinite(cx, &args[0]);
237 } else if method.ident.name == sym!(collect) {
238 let ty = cx.typeck_results().expr_ty(expr);
239 if INFINITE_COLLECTORS
241 .any(|diag_item| is_type_diagnostic_item(cx, ty, *diag_item))
243 return is_infinite(cx, &args[0]);
247 ExprKind::Binary(op, l, r) => {
248 if op.node.is_comparison() {
249 return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
251 }, // TODO: ExprKind::Loop + Match