rust-clippy/clippy_lints/src/infinite_iter.rs
2019-01-27 01:42:34 +01:00

257 lines
8.1 KiB
Rust

use crate::utils::{get_trait_def_id, higher, implements_trait, match_qpath, match_type, paths, span_lint};
use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::{declare_tool_lint, lint_array};
/// **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
/// (e.g. in event streams), in most cases this is simply an error.
///
/// **Known problems:** None.
///
/// **Example:**
/// ```rust
/// repeat(1_u8).iter().collect::<Vec<_>>()
/// ```
declare_clippy_lint! {
pub INFINITE_ITER,
correctness,
"infinite iteration"
}
/// **What it does:** Checks for iteration that may be infinite.
///
/// **Why is this bad?** While there may be places where this is acceptable
/// (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
/// [0..].iter().zip(infinite_iter.take_while(|x| x > 5))
/// ```
declare_clippy_lint! {
pub MAYBE_INFINITE_ITER,
pedantic,
"possible infinite iteration"
}
#[derive(Copy, Clone)]
pub struct Pass;
impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(INFINITE_ITER, MAYBE_INFINITE_ITER)
}
fn name(&self) -> &'static str {
"InfiniteIter"
}
}
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
let (lint, msg) = match complete_infinite_iter(cx, expr) {
Infinite => (INFINITE_ITER, "infinite iteration detected"),
MaybeInfinite => (MAYBE_INFINITE_ITER, "possible infinite iteration detected"),
Finite => {
return;
},
};
span_lint(cx, lint, expr.span, msg)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Finiteness {
Infinite,
MaybeInfinite,
Finite,
}
use self::Finiteness::{Finite, Infinite, MaybeInfinite};
impl Finiteness {
fn and(self, b: Self) -> Self {
match (self, b) {
(Finite, _) | (_, Finite) => Finite,
(MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
_ => Infinite,
}
}
fn or(self, b: Self) -> Self {
match (self, b) {
(Infinite, _) | (_, Infinite) => Infinite,
(MaybeInfinite, _) | (_, MaybeInfinite) => MaybeInfinite,
_ => Finite,
}
}
}
impl From<bool> for Finiteness {
fn from(b: bool) -> Self {
if b {
Infinite
} else {
Finite
}
}
}
/// This tells us what to look for to know if the iterator returned by
/// this method is infinite
#[derive(Copy, Clone)]
enum Heuristic {
/// infinite no matter what
Always,
/// infinite if the first argument is
First,
/// infinite if any of the supplied arguments is
Any,
/// infinite if all of the supplied arguments are
All,
}
use self::Heuristic::{All, Always, Any, First};
/// 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
/// is an upper bound, e.g. some methods can return a possibly
/// infinite iterator at worst, e.g. `take_while`.
static HEURISTICS: &[(&str, usize, Heuristic, Finiteness)] = &[
("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 {
match expr.node {
ExprKind::MethodCall(ref method, _, ref args) => {
for &(name, len, heuristic, cap) in HEURISTICS.iter() {
if method.ident.name == name && args.len() == len {
return (match heuristic {
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])),
})
.and(cap);
}
}
if method.ident.name == "flat_map" && args.len() == 2 {
if let ExprKind::Closure(_, _, body_id, _, _) = args[1].node {
let body = cx.tcx.hir().body(body_id);
return is_infinite(cx, &body.value);
}
}
Finite
},
ExprKind::Block(ref block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
ExprKind::Box(ref e) | ExprKind::AddrOf(_, ref e) => is_infinite(cx, e),
ExprKind::Call(ref path, _) => {
if let ExprKind::Path(ref qpath) = path.node {
match_qpath(qpath, &paths::REPEAT).into()
} else {
Finite
}
},
ExprKind::Struct(..) => higher::range(cx, expr).map_or(false, |r| r.end.is_none()).into(),
_ => Finite,
}
}
/// the names and argument lengths of methods that *may* exhaust their
/// iterators
static POSSIBLY_COMPLETING_METHODS: &[(&str, usize)] = &[
("find", 2),
("rfind", 2),
("position", 2),
("rposition", 2),
("any", 2),
("all", 2),
];
/// the names and argument lengths of methods that *always* exhaust
/// their iterators
static COMPLETING_METHODS: &[(&str, usize)] = &[
("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
static INFINITE_COLLECTORS: &[&[&str]] = &[
&paths::BINARY_HEAP,
&paths::BTREEMAP,
&paths::BTREESET,
&paths::HASHMAP,
&paths::HASHSET,
&paths::LINKED_LIST,
&paths::VEC,
&paths::VEC_DEQUE,
];
fn complete_infinite_iter(cx: &LateContext<'_, '_>, expr: &Expr) -> Finiteness {
match expr.node {
ExprKind::MethodCall(ref method, _, ref args) => {
for &(name, len) in COMPLETING_METHODS.iter() {
if method.ident.name == name && args.len() == len {
return is_infinite(cx, &args[0]);
}
}
for &(name, len) in POSSIBLY_COMPLETING_METHODS.iter() {
if method.ident.name == name && args.len() == len {
return MaybeInfinite.and(is_infinite(cx, &args[0]));
}
}
if method.ident.name == "last" && args.len() == 1 {
let not_double_ended = get_trait_def_id(cx, &paths::DOUBLE_ENDED_ITERATOR)
.map_or(false, |id| !implements_trait(cx, cx.tables.expr_ty(&args[0]), id, &[]));
if not_double_ended {
return is_infinite(cx, &args[0]);
}
} else if method.ident.name == "collect" {
let ty = cx.tables.expr_ty(expr);
if INFINITE_COLLECTORS.iter().any(|path| match_type(cx, ty, path)) {
return is_infinite(cx, &args[0]);
}
}
},
ExprKind::Binary(op, ref l, ref r) => {
if op.node.is_comparison() {
return is_infinite(cx, l).and(is_infinite(cx, r)).and(MaybeInfinite);
}
}, // TODO: ExprKind::Loop + Match
_ => (),
}
Finite
}