2 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
3 use rustc::{declare_tool_lint, lint_array};
5 use crate::utils::{get_trait_def_id, higher, implements_trait, match_qpath, match_type, paths, span_lint};
8 /// **What it does:** Checks for iteration that is guaranteed to be infinite.
10 /// **Why is this bad?** While there may be places where this is acceptable
11 /// (e.g., in event streams), in most cases this is simply an error.
13 /// **Known problems:** None.
19 /// iter::repeat(1_u8).collect::<Vec<_>>();
26 declare_clippy_lint! {
27 /// **What it does:** Checks for iteration that may be infinite.
29 /// **Why is this bad?** While there may be places where this is acceptable
30 /// (e.g., in event streams), in most cases this is simply an error.
32 /// **Known problems:** The code may have a condition to stop iteration, but
33 /// this lint is not clever enough to analyze it.
37 /// [0..].iter().zip(infinite_iter.take_while(|x| x > 5))
39 pub MAYBE_INFINITE_ITER,
41 "possible infinite iteration"
44 #[derive(Copy, Clone)]
47 impl LintPass for Pass {
48 fn get_lints(&self) -> LintArray {
49 lint_array!(INFINITE_ITER, MAYBE_INFINITE_ITER)
52 fn name(&self) -> &'static str {
57 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
58 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
59 let (lint, msg) = match complete_infinite_iter(cx, expr) {
60 Infinite => (INFINITE_ITER, "infinite iteration detected"),
61 MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
66 span_lint(cx, lint, expr.span, msg)
70 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
77 use self::Finiteness::{Finite, Infinite, MaybeInfinite};
80 fn and(self, b: Self) -> Self {
82 (Finite, _) | (_, Finite) => Finite,
83 (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
88 fn or(self, b: Self) -> Self {
90 (Infinite, _) | (_, Infinite) => Infinite,
91 (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
97 impl From<bool> for Finiteness {
98 fn from(b: bool) -> Self {
107 /// This tells us what to look for to know if the iterator returned by
108 /// this method is infinite
109 #[derive(Copy, Clone)]
111 /// infinite no matter what
113 /// infinite if the first argument is
115 /// infinite if any of the supplied arguments is
117 /// infinite if all of the supplied arguments are
121 use self::Heuristic::{All, Always, Any, First};
123 /// a slice of (method name, number of args, heuristic, bounds) tuples
124 /// that will be used to determine whether the method in question
125 /// returns an infinite or possibly infinite iterator. The finiteness
126 /// is an upper bound, e.g., some methods can return a possibly
127 /// infinite iterator at worst, e.g., `take_while`.
128 static HEURISTICS: &[(&str, usize, Heuristic, Finiteness)] = &[
129 ("zip", 2, All, Infinite),
130 ("chain", 2, Any, Infinite),
131 ("cycle", 1, Always, Infinite),
132 ("map", 2, First, Infinite),
133 ("by_ref", 1, First, Infinite),
134 ("cloned", 1, First, Infinite),
135 ("rev", 1, First, Infinite),
136 ("inspect", 1, First, Infinite),
137 ("enumerate", 1, First, Infinite),
138 ("peekable", 2, First, Infinite),
139 ("fuse", 1, First, Infinite),
140 ("skip", 2, First, Infinite),
141 ("skip_while", 1, First, Infinite),
142 ("filter", 2, First, Infinite),
143 ("filter_map", 2, First, Infinite),
144 ("flat_map", 2, First, Infinite),
145 ("unzip", 1, First, Infinite),
146 ("take_while", 2, First, MaybeInfinite),
147 ("scan", 3, First, MaybeInfinite),
150 fn is_infinite(cx: &LateContext<'_, '_>, expr: &Expr) -> Finiteness {
152 ExprKind::MethodCall(ref method, _, ref args) => {
153 for &(name, len, heuristic, cap) in HEURISTICS.iter() {
154 if method.ident.name == name && args.len() == len {
155 return (match heuristic {
157 First => is_infinite(cx, &args[0]),
158 Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])),
159 All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])),
164 if method.ident.name == "flat_map" && args.len() == 2 {
165 if let ExprKind::Closure(_, _, body_id, _, _) = args[1].node {
166 let body = cx.tcx.hir().body(body_id);
167 return is_infinite(cx, &body.value);
172 ExprKind::Block(ref block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
173 ExprKind::Box(ref e) | ExprKind::AddrOf(_, ref e) => is_infinite(cx, e),
174 ExprKind::Call(ref path, _) => {
175 if let ExprKind::Path(ref qpath) = path.node {
176 match_qpath(qpath, &paths::REPEAT).into()
181 ExprKind::Struct(..) => higher::range(cx, expr).map_or(false, |r| r.end.is_none()).into(),
186 /// the names and argument lengths of methods that *may* exhaust their
188 static POSSIBLY_COMPLETING_METHODS: &[(&str, usize)] = &[
197 /// the names and argument lengths of methods that *always* exhaust
199 static COMPLETING_METHODS: &[(&str, usize)] = &[
214 /// the paths of types that are known to be infinitely allocating
215 static INFINITE_COLLECTORS: &[&[&str]] = &[
226 fn complete_infinite_iter(cx: &LateContext<'_, '_>, expr: &Expr) -> Finiteness {
228 ExprKind::MethodCall(ref method, _, ref args) => {
229 for &(name, len) in COMPLETING_METHODS.iter() {
230 if method.ident.name == name && args.len() == len {
231 return is_infinite(cx, &args[0]);
234 for &(name, len) in POSSIBLY_COMPLETING_METHODS.iter() {
235 if method.ident.name == name && args.len() == len {
236 return MaybeInfinite.and(is_infinite(cx, &args[0]));
239 if method.ident.name == "last" && args.len() == 1 {
240 let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR)
241 .map_or(false, |id| !implements_trait(cx, cx.tables.expr_ty(&args[0]), id, &[]));
242 if not_double_ended {
243 return is_infinite(cx, &args[0]);
245 } else if method.ident.name == "collect" {
246 let ty = cx.tables.expr_ty(expr);
247 if INFINITE_COLLECTORS.iter().any(|path| match_type(cx, ty, path)) {
248 return is_infinite(cx, &args[0]);
252 ExprKind::Binary(op, ref l, ref r) => {
253 if op.node.is_comparison() {
254 return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
256 }, // TODO: ExprKind::Loop + Match