rust/clippy_lints/src/infinite_iter.rs

260 lines
8.4 KiB
Rust
Raw Normal View History

use clippy_utils::diagnostics::span_lint;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{higher, match_def_path, path_def_id, paths};
2020-02-21 02:39:38 -06:00
use rustc_hir::{BorrowKind, Expr, ExprKind};
2020-01-12 00:08:41 -06:00
use rustc_lint::{LateContext, LateLintPass};
2020-01-11 05:37:08 -06:00
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::{sym, Symbol};
2018-03-28 08:24:26 -05:00
declare_clippy_lint! {
/// ### What it does
/// Checks for iteration that is guaranteed to be infinite.
///
/// ### Why is this bad?
/// While there may be places where this is acceptable
2019-01-30 19:15:29 -06:00
/// (e.g., in event streams), in most cases this is simply an error.
///
/// ### Example
2019-03-05 16:23:50 -06:00
/// ```no_run
/// use std::iter;
///
/// iter::repeat(1_u8).collect::<Vec<_>>();
/// ```
#[clippy::version = "pre 1.29.0"]
pub INFINITE_ITER,
2018-03-28 08:24:26 -05:00
correctness,
"infinite iteration"
}
2018-03-28 08:24:26 -05:00
declare_clippy_lint! {
/// ### What it does
/// Checks for iteration that may be infinite.
///
/// ### Why is this bad?
/// While there may be places where this is acceptable
2019-01-30 19:15:29 -06:00
/// (e.g., in event streams), in most cases this is simply an error.
///
/// ### Known problems
/// The code may have a condition to stop iteration, but
/// this lint is not clever enough to analyze it.
///
/// ### Example
/// ```rust
/// let infinite_iter = 0..;
/// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5));
/// ```
#[clippy::version = "pre 1.29.0"]
pub MAYBE_INFINITE_ITER,
2018-03-28 08:24:26 -05:00
pedantic,
"possible infinite iteration"
}
2019-04-08 15:43:55 -05:00
declare_lint_pass!(InfiniteIter => [INFINITE_ITER, MAYBE_INFINITE_ITER]);
impl<'tcx> LateLintPass<'tcx> for InfiniteIter {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
let (lint, msg) = match complete_infinite_iter(cx, expr) {
2017-08-26 11:46:42 -05:00
Infinite => (INFINITE_ITER, "infinite iteration detected"),
2017-09-03 16:15:15 -05:00
MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
Finite => {
return;
},
};
span_lint(cx, lint, expr.span, msg);
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
2017-08-26 11:46:42 -05:00
enum Finiteness {
Infinite,
MaybeInfinite,
2017-09-03 16:15:15 -05:00
Finite,
}
2017-09-05 04:33:04 -05:00
use self::Finiteness::{Finite, Infinite, MaybeInfinite};
2017-08-26 11:46:42 -05:00
impl Finiteness {
#[must_use]
fn and(self, b: Self) -> Self {
match (self, b) {
2017-08-26 11:46:42 -05:00
(Finite, _) | (_, Finite) => Finite,
2017-09-05 04:33:04 -05:00
(MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
2017-09-03 16:15:15 -05:00
_ => Infinite,
}
}
#[must_use]
fn or(self, b: Self) -> Self {
match (self, b) {
2017-08-26 11:46:42 -05:00
(Infinite, _) | (_, Infinite) => Infinite,
2017-09-05 04:33:04 -05:00
(MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
2017-09-03 16:15:15 -05:00
_ => Finite,
}
}
}
2017-08-26 11:46:42 -05:00
impl From<bool> for Finiteness {
#[must_use]
fn from(b: bool) -> Self {
if b { Infinite } else { Finite }
}
}
2017-08-26 11:46:42 -05:00
/// This tells us what to look for to know if the iterator returned by
/// this method is infinite
#[derive(Copy, Clone)]
enum Heuristic {
2017-08-26 11:46:42 -05:00
/// infinite no matter what
Always,
2017-08-26 11:46:42 -05:00
/// infinite if the first argument is
First,
2017-08-26 11:46:42 -05:00
/// infinite if any of the supplied arguments is
Any,
2017-08-26 11:46:42 -05:00
/// infinite if all of the supplied arguments are
2017-09-03 16:15:15 -05:00
All,
}
2017-09-05 04:33:04 -05:00
use self::Heuristic::{All, Always, Any, First};
2017-08-26 11:46:42 -05:00
/// a slice of (method name, number of args, heuristic, bounds) tuples
/// that will be used to determine whether the method in question
/// returns an infinite or possibly infinite iterator. The finiteness
2019-01-30 19:15:29 -06:00
/// is an upper bound, e.g., some methods can return a possibly
/// infinite iterator at worst, e.g., `take_while`.
2019-05-17 16:53:54 -05:00
const HEURISTICS: [(&str, usize, Heuristic, Finiteness); 19] = [
("zip", 2, All, Infinite),
("chain", 2, Any, Infinite),
("cycle", 1, Always, Infinite),
("map", 2, First, Infinite),
("by_ref", 1, First, Infinite),
("cloned", 1, First, Infinite),
("rev", 1, First, Infinite),
("inspect", 1, First, Infinite),
("enumerate", 1, First, Infinite),
("peekable", 2, First, Infinite),
("fuse", 1, First, Infinite),
("skip", 2, First, Infinite),
("skip_while", 1, First, Infinite),
("filter", 2, First, Infinite),
("filter_map", 2, First, Infinite),
("flat_map", 2, First, Infinite),
("unzip", 1, First, Infinite),
("take_while", 2, First, MaybeInfinite),
("scan", 3, First, MaybeInfinite),
];
fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
2019-09-27 10:16:06 -05:00
match expr.kind {
ExprKind::MethodCall(method, args, _) => {
2019-05-17 18:34:52 -05:00
for &(name, len, heuristic, cap) in &HEURISTICS {
2019-05-17 16:53:54 -05:00
if method.ident.name.as_str() == name && args.len() == len {
return (match heuristic {
2017-09-05 04:33:04 -05:00
Always => Infinite,
First => is_infinite(cx, &args[0]),
Any => is_infinite(cx, &args[0]).or(is_infinite(cx, &args[1])),
All => is_infinite(cx, &args[0]).and(is_infinite(cx, &args[1])),
2018-11-27 14:14:15 -06:00
})
.and(cap);
}
}
2019-05-17 16:53:54 -05:00
if method.ident.name == sym!(flat_map) && args.len() == 2 {
2019-09-27 10:16:06 -05:00
if let ExprKind::Closure(_, _, body_id, _, _) = args[1].kind {
let body = cx.tcx.hir().body(body_id);
return is_infinite(cx, &body.value);
}
}
2017-08-26 11:46:42 -05:00
Finite
},
ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
2022-01-06 12:41:17 -06:00
ExprKind::Call(path, _) => path_def_id(cx, path)
.map_or(false, |id| match_def_path(cx, id, &paths::ITER_REPEAT))
.into(),
2021-08-08 09:49:13 -05:00
ExprKind::Struct(..) => higher::Range::hir(expr).map_or(false, |r| r.end.is_none()).into(),
2017-09-03 16:15:15 -05:00
_ => Finite,
}
}
2017-08-26 11:46:42 -05:00
/// the names and argument lengths of methods that *may* exhaust their
/// iterators
2019-05-17 16:53:54 -05:00
const POSSIBLY_COMPLETING_METHODS: [(&str, usize); 6] = [
("find", 2),
("rfind", 2),
("position", 2),
("rposition", 2),
("any", 2),
("all", 2),
];
2017-08-26 11:46:42 -05:00
/// the names and argument lengths of methods that *always* exhaust
/// their iterators
2019-05-17 16:53:54 -05:00
const COMPLETING_METHODS: [(&str, usize); 12] = [
("count", 1),
("fold", 3),
("for_each", 2),
("partition", 2),
("max", 1),
("max_by", 2),
("max_by_key", 2),
("min", 1),
("min_by", 2),
("min_by_key", 2),
("sum", 1),
("product", 1),
];
/// the paths of types that are known to be infinitely allocating
const INFINITE_COLLECTORS: &[Symbol] = &[
sym::BinaryHeap,
sym::BTreeMap,
sym::BTreeSet,
2021-10-02 18:51:01 -05:00
sym::HashMap,
sym::HashSet,
sym::LinkedList,
2021-10-02 18:51:01 -05:00
sym::Vec,
sym::VecDeque,
];
fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
2019-09-27 10:16:06 -05:00
match expr.kind {
ExprKind::MethodCall(method, args, _) => {
2019-05-17 18:34:52 -05:00
for &(name, len) in &COMPLETING_METHODS {
2019-05-17 16:53:54 -05:00
if method.ident.name.as_str() == name && args.len() == len {
return is_infinite(cx, &args[0]);
}
}
2019-05-17 18:34:52 -05:00
for &(name, len) in &POSSIBLY_COMPLETING_METHODS {
2019-05-17 16:53:54 -05:00
if method.ident.name.as_str() == name && args.len() == len {
2017-08-26 11:46:42 -05:00
return MaybeInfinite.and(is_infinite(cx, &args[0]));
}
}
2019-05-17 16:53:54 -05:00
if method.ident.name == sym!(last) && args.len() == 1 {
let not_double_ended = cx
.tcx
.get_diagnostic_item(sym::DoubleEndedIterator)
.map_or(false, |id| {
!implements_trait(cx, cx.typeck_results().expr_ty(&args[0]), id, &[])
});
2017-09-03 16:58:27 -05:00
if not_double_ended {
2017-09-05 04:33:04 -05:00
return is_infinite(cx, &args[0]);
2017-09-03 16:58:27 -05:00
}
2019-05-17 16:53:54 -05:00
} else if method.ident.name == sym!(collect) {
2020-07-17 03:47:04 -05:00
let ty = cx.typeck_results().expr_ty(expr);
if INFINITE_COLLECTORS
.iter()
.any(|diag_item| is_type_diagnostic_item(cx, ty, *diag_item))
{
return is_infinite(cx, &args[0]);
}
}
},
ExprKind::Binary(op, l, r) => {
2018-11-27 14:14:15 -06:00
if op.node.is_comparison() {
return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
}
2018-07-12 02:30:57 -05:00
}, // TODO: ExprKind::Loop + Match
2017-09-03 16:15:15 -05:00
_ => (),
}
2017-08-26 11:46:42 -05:00
Finite
}