From 96e2bc80f556e54b95ae224dc2f59cfa47397a38 Mon Sep 17 00:00:00 2001 From: CrazyRoka <rokarostuk@gmail.com> Date: Fri, 24 Apr 2020 00:28:18 +0300 Subject: [PATCH] Added lint `match_vec_item` --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 5 ++ clippy_lints/src/match_vec_item.rs | 111 +++++++++++++++++++++++++++++ src/lintlist/mod.rs | 7 ++ tests/ui/match_vec_item.rs | 109 ++++++++++++++++++++++++++++ tests/ui/match_vec_item.stderr | 27 +++++++ 6 files changed, 260 insertions(+) create mode 100644 clippy_lints/src/match_vec_item.rs create mode 100644 tests/ui/match_vec_item.rs create mode 100644 tests/ui/match_vec_item.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index abd7167502b..3ba2c51623b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1352,6 +1352,7 @@ Released 2018-09-13 [`match_ref_pats`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_ref_pats [`match_same_arms`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_same_arms [`match_single_binding`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_single_binding +[`match_vec_item`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_vec_item [`match_wild_err_arm`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_wild_err_arm [`maybe_infinite_iter`]: https://rust-lang.github.io/rust-clippy/master/index.html#maybe_infinite_iter [`mem_discriminant_non_enum`]: https://rust-lang.github.io/rust-clippy/master/index.html#mem_discriminant_non_enum diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index dee4188b75f..79bf13a1005 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -249,6 +249,7 @@ mod macro_use; mod main_recursion; mod map_clone; mod map_unit_fn; +mod match_vec_item; mod matches; mod mem_discriminant; mod mem_forget; @@ -629,6 +630,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &map_clone::MAP_CLONE, &map_unit_fn::OPTION_MAP_UNIT_FN, &map_unit_fn::RESULT_MAP_UNIT_FN, + &match_vec_item::MATCH_VEC_ITEM, &matches::INFALLIBLE_DESTRUCTURING_MATCH, &matches::MATCH_AS_REF, &matches::MATCH_BOOL, @@ -1060,6 +1062,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box future_not_send::FutureNotSend); store.register_late_pass(|| box utils::internal_lints::CollapsibleCalls); store.register_late_pass(|| box if_let_mutex::IfLetMutex); + store.register_late_pass(|| box match_vec_item::MatchVecItem); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(&arithmetic::FLOAT_ARITHMETIC), @@ -1277,6 +1280,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&map_clone::MAP_CLONE), LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN), LintId::of(&map_unit_fn::RESULT_MAP_UNIT_FN), + LintId::of(&match_vec_item::MATCH_VEC_ITEM), LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), LintId::of(&matches::MATCH_AS_REF), LintId::of(&matches::MATCH_BOOL), @@ -1469,6 +1473,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(&loops::WHILE_LET_ON_ITERATOR), LintId::of(&main_recursion::MAIN_RECURSION), LintId::of(&map_clone::MAP_CLONE), + LintId::of(&match_vec_item::MATCH_VEC_ITEM), LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH), LintId::of(&matches::MATCH_BOOL), LintId::of(&matches::MATCH_OVERLAPPING_ARM), diff --git a/clippy_lints/src/match_vec_item.rs b/clippy_lints/src/match_vec_item.rs new file mode 100644 index 00000000000..7ea5a24abbd --- /dev/null +++ b/clippy_lints/src/match_vec_item.rs @@ -0,0 +1,111 @@ +use crate::utils::{is_wild, span_lint_and_help}; +use if_chain::if_chain; +use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::{self, AdtDef}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** Checks for `match vec[idx]` or `match vec[n..m]`. + /// + /// **Why is this bad?** This can panic at runtime. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Bad + /// match arr[idx] { + /// 0 => println!("{}", 0), + /// 1 => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + /// Use instead: + /// ```rust, no_run + /// let arr = vec![0, 1, 2, 3]; + /// let idx = 1; + /// + /// // Good + /// match arr.get(idx) { + /// Some(0) => println!("{}", 0), + /// Some(1) => println!("{}", 3), + /// _ => {}, + /// } + /// ``` + pub MATCH_VEC_ITEM, + style, + "match vector by indexing can panic" +} + +declare_lint_pass!(MatchVecItem => [MATCH_VEC_ITEM]); + +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for MatchVecItem { + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) { + if_chain! { + if !in_external_macro(cx.sess(), expr.span); + if let ExprKind::Match(ref ex, ref arms, MatchSource::Normal) = expr.kind; + if contains_wild_arm(arms); + if is_vec_indexing(cx, ex); + + then { + span_lint_and_help( + cx, + MATCH_VEC_ITEM, + expr.span, + "indexing vector may panic", + None, + "consider using `get(..)` instead.", + ); + } + } + } +} + +fn contains_wild_arm(arms: &[Arm<'_>]) -> bool { + arms.iter().any(|arm| is_wild(&arm.pat) && is_unit_expr(arm.body)) +} + +fn is_vec_indexing<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if_chain! { + if let ExprKind::Index(ref array, _) = expr.kind; + let ty = cx.tables.expr_ty(array); + if let ty::Adt(def, _) = ty.kind; + if is_vec(cx, def); + + then { + return true; + } + } + + false +} + +fn is_vec<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, def: &'a AdtDef) -> bool { + if_chain! { + let def_path = cx.tcx.def_path(def.did); + if def_path.data.len() == 2; + if let Some(module) = def_path.data.get(0); + if module.data.as_symbol() == sym!(vec); + if let Some(name) = def_path.data.get(1); + if name.data.as_symbol() == sym!(Vec); + + then { + return true; + } + } + + false +} + +fn is_unit_expr(expr: &Expr<'_>) -> bool { + match expr.kind { + ExprKind::Tup(ref v) if v.is_empty() => true, + ExprKind::Block(ref b, _) if b.stmts.is_empty() && b.expr.is_none() => true, + _ => false, + } +} diff --git a/src/lintlist/mod.rs b/src/lintlist/mod.rs index 2c466aa20c6..7c9e0448950 100644 --- a/src/lintlist/mod.rs +++ b/src/lintlist/mod.rs @@ -1172,6 +1172,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![ deprecation: None, module: "matches", }, + Lint { + name: "match_vec_item", + group: "style", + desc: "match vector by indexing can panic", + deprecation: None, + module: "match_vec_item", + }, Lint { name: "match_wild_err_arm", group: "style", diff --git a/tests/ui/match_vec_item.rs b/tests/ui/match_vec_item.rs new file mode 100644 index 00000000000..8e7c098d75c --- /dev/null +++ b/tests/ui/match_vec_item.rs @@ -0,0 +1,109 @@ +#![warn(clippy::match_vec_item)] + +fn main() { + match_with_wildcard(); + match_without_wildcard(); + match_wildcard_and_action(); + match_with_get(); + match_with_array(); +} + +fn match_with_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 1; + + // Lint, may panic + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Lint, may panic + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} + +fn match_without_wildcard() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 2; + + // Ok + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + num => {}, + } + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + [ref sub @ ..] => {}, + } +} + +fn match_wildcard_and_action() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => println!("Hello, World!"), + } + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => println!("Hello, World!"), + } +} + +fn match_with_get() { + let arr = vec![0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr.get(idx) { + Some(0) => println!("0"), + Some(1) => println!("1"), + _ => {}, + } + + // Ok + match arr.get(range) { + Some(&[0, 1]) => println!("0 1"), + Some(&[1, 2]) => println!("1 2"), + _ => {}, + } +} + +fn match_with_array() { + let arr = [0, 1, 2, 3]; + let range = 1..3; + let idx = 3; + + // Ok + match arr[idx] { + 0 => println!("0"), + 1 => println!("1"), + _ => {}, + } + + // Ok + match arr[range] { + [0, 1] => println!("0 1"), + [1, 2] => println!("1 2"), + _ => {}, + } +} diff --git a/tests/ui/match_vec_item.stderr b/tests/ui/match_vec_item.stderr new file mode 100644 index 00000000000..2494c3143f8 --- /dev/null +++ b/tests/ui/match_vec_item.stderr @@ -0,0 +1,27 @@ +error: indexing vector may panic + --> $DIR/match_vec_item.rs:17:5 + | +LL | / match arr[idx] { +LL | | 0 => println!("0"), +LL | | 1 => println!("1"), +LL | | _ => {}, +LL | | } + | |_____^ + | + = note: `-D clippy::match-vec-item` implied by `-D warnings` + = help: consider using `get(..)` instead. + +error: indexing vector may panic + --> $DIR/match_vec_item.rs:24:5 + | +LL | / match arr[range] { +LL | | [0, 1] => println!("0 1"), +LL | | [1, 2] => println!("1 2"), +LL | | _ => {}, +LL | | } + | |_____^ + | + = help: consider using `get(..)` instead. + +error: aborting due to 2 previous errors +