]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/infinite_iter.rs
Auto merge of #9539 - Jarcho:ice_9445, r=flip1995
[rust.git] / clippy_lints / src / infinite_iter.rs
1 use clippy_utils::diagnostics::span_lint;
2 use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
3 use clippy_utils::{higher, match_def_path, path_def_id, paths};
4 use rustc_hir::{BorrowKind, Closure, 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};
8
9 declare_clippy_lint! {
10     /// ### What it does
11     /// Checks for iteration that is guaranteed to be infinite.
12     ///
13     /// ### Why is this bad?
14     /// While there may be places where this is acceptable
15     /// (e.g., in event streams), in most cases this is simply an error.
16     ///
17     /// ### Example
18     /// ```no_run
19     /// use std::iter;
20     ///
21     /// iter::repeat(1_u8).collect::<Vec<_>>();
22     /// ```
23     #[clippy::version = "pre 1.29.0"]
24     pub INFINITE_ITER,
25     correctness,
26     "infinite iteration"
27 }
28
29 declare_clippy_lint! {
30     /// ### What it does
31     /// Checks for iteration that may be infinite.
32     ///
33     /// ### Why is this bad?
34     /// While there may be places where this is acceptable
35     /// (e.g., in event streams), in most cases this is simply an error.
36     ///
37     /// ### Known problems
38     /// The code may have a condition to stop iteration, but
39     /// this lint is not clever enough to analyze it.
40     ///
41     /// ### Example
42     /// ```rust
43     /// let infinite_iter = 0..;
44     /// # #[allow(unused)]
45     /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
46     /// ```
47     #[clippy::version = "pre 1.29.0"]
48     pub MAYBE_INFINITE_ITER,
49     pedantic,
50     "possible infinite iteration"
51 }
52
53 declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]);
54
55 impl<'tcx> LateLintPass<'tcx> for InfiniteIter {
56     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
57         let (lint, msg) = match complete_infinite_iter(cx, expr) {
58             Infinite => (INFINITE_ITER, "infinite iteration detected"),
59             MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
60             Finite => {
61                 return;
62             },
63         };
64         span_lint(cx, lint, expr.span, msg);
65     }
66 }
67
68 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
69 enum Finiteness {
70     Infinite,
71     MaybeInfinite,
72     Finite,
73 }
74
75 use self::Finiteness::{Finite, Infinite, MaybeInfinite};
76
77 impl Finiteness {
78     #[must_use]
79     fn and(self, b: Self) -> Self {
80         match (self, b) {
81             (Finite, _) | (_, Finite) => Finite,
82             (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
83             _ => Infinite,
84         }
85     }
86
87     #[must_use]
88     fn or(self, b: Self) -> Self {
89         match (self, b) {
90             (Infinite, _) | (_, Infinite) => Infinite,
91             (MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
92             _ => Finite,
93         }
94     }
95 }
96
97 impl From<bool> for Finiteness {
98     #[must_use]
99     fn from(b: bool) -> Self {
100         if b { Infinite } else { Finite }
101     }
102 }
103
104 /// This tells us what to look for to know if the iterator returned by
105 /// this method is infinite
106 #[derive(Copy, Clone)]
107 enum Heuristic {
108     /// infinite no matter what
109     Always,
110     /// infinite if the first argument is
111     First,
112     /// infinite if any of the supplied arguments is
113     Any,
114     /// infinite if all of the supplied arguments are
115     All,
116 }
117
118 use self::Heuristic::{All, Always, Any, First};
119
120 /// a slice of (method name, number of args, heuristic, bounds) tuples
121 /// that will be used to determine whether the method in question
122 /// returns an infinite or possibly infinite iterator. The finiteness
123 /// is an upper bound, e.g., some methods can return a possibly
124 /// infinite iterator at worst, e.g., `take_while`.
125 const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
126     ("zip", 1, All, Infinite),
127     ("chain", 1, Any, Infinite),
128     ("cycle", 0, Always, Infinite),
129     ("map", 1, First, Infinite),
130     ("by_ref", 0, First, Infinite),
131     ("cloned", 0, First, Infinite),
132     ("rev", 0, First, Infinite),
133     ("inspect", 0, First, Infinite),
134     ("enumerate", 0, First, Infinite),
135     ("peekable", 1, First, Infinite),
136     ("fuse", 0, First, Infinite),
137     ("skip", 1, First, Infinite),
138     ("skip_while", 0, First, Infinite),
139     ("filter", 1, First, Infinite),
140     ("filter_map", 1, First, Infinite),
141     ("flat_map", 1, First, Infinite),
142     ("unzip", 0, First, Infinite),
143     ("take_while", 1, First, MaybeInfinite),
144     ("scan", 2, First, MaybeInfinite),
145 ];
146
147 fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
148     match expr.kind {
149         ExprKind::MethodCall(method, receiver, args, _) => {
150             for &(name, len, heuristic, cap) in &HEURISTICS {
151                 if method.ident.name.as_str() == name && args.len() == len {
152                     return (match heuristic {
153                         Always => Infinite,
154                         First => is_infinite(cx, receiver),
155                         Any => is_infinite(cx, receiver).or(is_infinite(cx, &args[0])),
156                         All => is_infinite(cx, receiver).and(is_infinite(cx, &args[0])),
157                     })
158                     .and(cap);
159                 }
160             }
161             if method.ident.name == sym!(flat_map) && args.len() == 1 {
162                 if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind {
163                     let body = cx.tcx.hir().body(body);
164                     return is_infinite(cx, body.value);
165                 }
166             }
167             Finite
168         },
169         ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
170         ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
171         ExprKind::Call(path, _) => path_def_id(cx, path)
172             .map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT))
173             .into(),
174         ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
175         _ => Finite,
176     }
177 }
178
179 /// the names and argument lengths of methods that *may* exhaust their
180 /// iterators
181 const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
182     ("find", 1),
183     ("rfind", 1),
184     ("position", 1),
185     ("rposition", 1),
186     ("any", 1),
187     ("all", 1),
188 ];
189
190 /// the names and argument lengths of methods that *always* exhaust
191 /// their iterators
192 const COMPLETING_METHODS: [(&str, usize); 12] = [
193     ("count", 0),
194     ("fold", 2),
195     ("for_each", 1),
196     ("partition", 1),
197     ("max", 0),
198     ("max_by", 1),
199     ("max_by_key", 1),
200     ("min", 0),
201     ("min_by", 1),
202     ("min_by_key", 1),
203     ("sum", 0),
204     ("product", 0),
205 ];
206
207 /// the paths of types that are known to be infinitely allocating
208 const INFINITE_COLLECTORS: &[Symbol] = &[
209     sym::BinaryHeap,
210     sym::BTreeMap,
211     sym::BTreeSet,
212     sym::HashMap,
213     sym::HashSet,
214     sym::LinkedList,
215     sym::Vec,
216     sym::VecDeque,
217 ];
218
219 fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
220     match expr.kind {
221         ExprKind::MethodCall(method, receiver, args, _) => {
222             for &(name, len) in &COMPLETING_METHODS {
223                 if method.ident.name.as_str() == name && args.len() == len {
224                     return is_infinite(cx, receiver);
225                 }
226             }
227             for &(name, len) in &POSSIBLY_COMPLETING_METHODS {
228                 if method.ident.name.as_str() == name && args.len() == len {
229                     return MaybeInfinite.and(is_infinite(cx, receiver));
230                 }
231             }
232             if method.ident.name == sym!(last) && args.is_empty() {
233                 let not_double_ended = cx
234                     .tcx
235                     .get_diagnostic_item(sym::DoubleEndedIterator)
236                     .map_or(false, |id| {
237                         !implements_trait(cx, cx.typeck_results().expr_ty(receiver), id, &[])
238                     });
239                 if not_double_ended {
240                     return is_infinite(cx, receiver);
241                 }
242             } else if method.ident.name == sym!(collect) {
243                 let ty = cx.typeck_results().expr_ty(expr);
244                 if INFINITE_COLLECTORS
245                     .iter()
246                     .any(|diag_item| is_type_diagnostic_item(cx, ty, *diag_item))
247                 {
248                     return is_infinite(cx, receiver);
249                 }
250             }
251         },
252         ExprKind::Binary(op, l, r) => {
253             if op.node.is_comparison() {
254                 return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
255             }
256         }, // TODO: ExprKind::Loop + Match
257         _ => (),
258     }
259     Finite
260 }