|
| 1 | +// Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT |
| 2 | +// file at the top-level directory of this distribution. |
| 3 | +// |
| 4 | +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| 5 | +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| 6 | +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| 7 | +// option. This file may not be copied, modified, or distributed |
| 8 | +// except according to those terms. |
| 9 | + |
| 10 | +use crate::rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; |
| 11 | +use crate::rustc::lint::{LateContext, LateLintPass, LintArray, LintPass}; |
| 12 | +use crate::rustc::{declare_tool_lint, lint_array}; |
| 13 | +use crate::rustc::hir::*; |
| 14 | +use if_chain::if_chain; |
| 15 | +use crate::syntax_pos::symbol::Symbol; |
| 16 | +use crate::syntax::ast::{LitKind, NodeId}; |
| 17 | +use crate::syntax::source_map::Span; |
| 18 | +use crate::utils::{match_qpath, span_lint_and_then, SpanlessEq}; |
| 19 | +use crate::utils::get_enclosing_block; |
| 20 | +use crate::rustc_errors::{Applicability}; |
| 21 | + |
| 22 | +/// **What it does:** Checks slow zero-filled vector initialization |
| 23 | +/// |
| 24 | +/// **Why is this bad?** This structures are non-idiomatic and less efficient than simply using |
| 25 | +/// `vec![len; 0]`. |
| 26 | +/// |
| 27 | +/// **Known problems:** None. |
| 28 | +/// |
| 29 | +/// **Example:** |
| 30 | +/// ```rust |
| 31 | +/// let mut vec1 = Vec::with_capacity(len); |
| 32 | +/// vec1.resize(len, 0); |
| 33 | +/// |
| 34 | +/// let mut vec2 = Vec::with_capacity(len); |
| 35 | +/// vec2.extend(repeat(0).take(len)) |
| 36 | +/// ``` |
| 37 | +declare_clippy_lint! { |
| 38 | + pub SLOW_VECTOR_INITIALIZATION, |
| 39 | + perf, |
| 40 | + "slow or unsafe vector initialization" |
| 41 | +} |
| 42 | + |
| 43 | +#[derive(Copy, Clone, Default)] |
| 44 | +pub struct Pass; |
| 45 | + |
| 46 | +impl LintPass for Pass { |
| 47 | + fn get_lints(&self) -> LintArray { |
| 48 | + lint_array!(SLOW_VECTOR_INITIALIZATION) |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +/// VecInitialization contains data regarding a vector initialized with `with_capacity` and then |
| 53 | +/// assigned to a variable. For example, `let mut vec = Vec::with_capacity(0)` or |
| 54 | +/// `vec = Vec::with_capacity(0)` |
| 55 | +struct VecInitialization<'tcx> { |
| 56 | + /// Symbol of the local variable name |
| 57 | + variable_name: Symbol, |
| 58 | + |
| 59 | + /// Reference to the expression which initializes the vector |
| 60 | + initialization_expr: &'tcx Expr, |
| 61 | + |
| 62 | + /// Reference to the expression used as argument on `with_capacity` call. This is used |
| 63 | + /// to only match slow zero-filling idioms of the same length than vector initialization. |
| 64 | + len_expr: &'tcx Expr, |
| 65 | +} |
| 66 | + |
| 67 | +/// Type of slow initialization |
| 68 | +enum InitializationType<'tcx> { |
| 69 | + /// Extend is a slow initialization with the form `vec.extend(repeat(0).take(..))` |
| 70 | + Extend(&'tcx Expr), |
| 71 | + |
| 72 | + /// Resize is a slow initialization with the form `vec.resize(.., 0)` |
| 73 | + Resize(&'tcx Expr), |
| 74 | +} |
| 75 | + |
| 76 | +impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass { |
| 77 | + fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) { |
| 78 | + // Matches initialization on reassignements. For example: `vec = Vec::with_capacity(100)` |
| 79 | + if_chain! { |
| 80 | + if let ExprKind::Assign(ref left, ref right) = expr.node; |
| 81 | + |
| 82 | + // Extract variable name |
| 83 | + if let ExprKind::Path(QPath::Resolved(_, ref path)) = left.node; |
| 84 | + if let Some(variable_name) = path.segments.get(0); |
| 85 | + |
| 86 | + // Extract len argument |
| 87 | + if let Some(ref len_arg) = Pass::is_vec_with_capacity(right); |
| 88 | + |
| 89 | + then { |
| 90 | + let vi = VecInitialization { |
| 91 | + variable_name: variable_name.ident.name, |
| 92 | + initialization_expr: right, |
| 93 | + len_expr: len_arg, |
| 94 | + }; |
| 95 | + |
| 96 | + Pass::search_slow_zero_filling(cx, vi, expr.id, expr.span); |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt) { |
| 102 | + // Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)` |
| 103 | + if_chain! { |
| 104 | + if let StmtKind::Decl(ref decl, _) = stmt.node; |
| 105 | + if let DeclKind::Local(ref local) = decl.node; |
| 106 | + if let PatKind::Binding(BindingAnnotation::Mutable, _, variable_name, None) = local.pat.node; |
| 107 | + if let Some(ref init) = local.init; |
| 108 | + if let Some(ref len_arg) = Pass::is_vec_with_capacity(init); |
| 109 | + |
| 110 | + then { |
| 111 | + let vi = VecInitialization { |
| 112 | + variable_name: variable_name.name, |
| 113 | + initialization_expr: init, |
| 114 | + len_expr: len_arg, |
| 115 | + }; |
| 116 | + |
| 117 | + Pass::search_slow_zero_filling(cx, vi, stmt.node.id(), stmt.span); |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +impl Pass { |
| 124 | + /// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression |
| 125 | + /// of the first argument of `with_capacity` call if it matches or `None` if it does not. |
| 126 | + fn is_vec_with_capacity(expr: &Expr) -> Option<&Expr> { |
| 127 | + if_chain! { |
| 128 | + if let ExprKind::Call(ref func, ref args) = expr.node; |
| 129 | + if let ExprKind::Path(ref path) = func.node; |
| 130 | + if match_qpath(path, &["Vec", "with_capacity"]); |
| 131 | + if args.len() == 1; |
| 132 | + |
| 133 | + then { |
| 134 | + return Some(&args[0]); |
| 135 | + } |
| 136 | + } |
| 137 | + |
| 138 | + None |
| 139 | + } |
| 140 | + |
| 141 | + /// Search for slow zero filling vector initialization for the given vector |
| 142 | + fn search_slow_zero_filling<'tcx>( |
| 143 | + cx: &LateContext<'_, 'tcx>, |
| 144 | + vec_initialization: VecInitialization<'tcx>, |
| 145 | + parent_node: NodeId, |
| 146 | + parent_span: Span |
| 147 | + ) { |
| 148 | + let enclosing_body = get_enclosing_block(cx, parent_node); |
| 149 | + |
| 150 | + if enclosing_body.is_none() { |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + let mut v = SlowInitializationVisitor { |
| 155 | + cx, |
| 156 | + vec_ini: vec_initialization, |
| 157 | + slow_expression: None, |
| 158 | + initialization_found: false, |
| 159 | + }; |
| 160 | + |
| 161 | + v.visit_block(enclosing_body.unwrap()); |
| 162 | + |
| 163 | + if let Some(ref repeat_expr) = v.slow_expression { |
| 164 | + span_lint_and_then( |
| 165 | + cx, |
| 166 | + SLOW_VECTOR_INITIALIZATION, |
| 167 | + parent_span, |
| 168 | + "detected slow zero-filling initialization", |
| 169 | + |db| { |
| 170 | + db.span_suggestion_with_applicability(v.vec_ini.initialization_expr.span, "consider replacing with", "vec![0; ..]".to_string(), Applicability::Unspecified); |
| 171 | + |
| 172 | + match repeat_expr { |
| 173 | + InitializationType::Extend(e) => { |
| 174 | + db.span_note(e.span, "extended here with .. 0"); |
| 175 | + }, |
| 176 | + InitializationType::Resize(e) => { |
| 177 | + db.span_note(e.span, "resize here with .. 0"); |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + ); |
| 182 | + } |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +/// SlowInitializationVisitor searches for slow zero filling vector initialization, for the given |
| 187 | +/// vector. |
| 188 | +struct SlowInitializationVisitor<'a, 'tcx: 'a> { |
| 189 | + cx: &'a LateContext<'a, 'tcx>, |
| 190 | + |
| 191 | + /// Contains the information |
| 192 | + vec_ini: VecInitialization<'tcx>, |
| 193 | + |
| 194 | + /// Contains, if found, the slow initialization expression |
| 195 | + slow_expression: Option<InitializationType<'tcx>>, |
| 196 | + |
| 197 | + /// true if the initialization of the vector has been found on the visited block |
| 198 | + initialization_found: bool, |
| 199 | +} |
| 200 | + |
| 201 | +impl<'a, 'tcx> SlowInitializationVisitor<'a, 'tcx> { |
| 202 | + /// Checks if the given expression is extending a vector with `repeat(0).take(..)` |
| 203 | + fn search_slow_extend_filling(&mut self, expr: &'tcx Expr) { |
| 204 | + if_chain! { |
| 205 | + if self.initialization_found; |
| 206 | + if let ExprKind::MethodCall(ref path, _, ref args) = expr.node; |
| 207 | + if let ExprKind::Path(ref qpath_subj) = args[0].node; |
| 208 | + if match_qpath(&qpath_subj, &[&self.vec_ini.variable_name.to_string()]); |
| 209 | + if path.ident.name == "extend"; |
| 210 | + if let Some(ref extend_arg) = args.get(1); |
| 211 | + if self.is_repeat_take(extend_arg); |
| 212 | + |
| 213 | + then { |
| 214 | + self.slow_expression = Some(InitializationType::Extend(expr)); |
| 215 | + } |
| 216 | + } |
| 217 | + } |
| 218 | + |
| 219 | + /// Checks if the given expression is resizing a vector with 0 |
| 220 | + fn search_slow_resize_filling(&mut self, expr: &'tcx Expr) { |
| 221 | + if_chain! { |
| 222 | + if self.initialization_found; |
| 223 | + if let ExprKind::MethodCall(ref path, _, ref args) = expr.node; |
| 224 | + if let ExprKind::Path(ref qpath_subj) = args[0].node; |
| 225 | + if match_qpath(&qpath_subj, &[&self.vec_ini.variable_name.to_string()]); |
| 226 | + if path.ident.name == "resize"; |
| 227 | + if let (Some(ref len_arg), Some(fill_arg)) = (args.get(1), args.get(2)); |
| 228 | + |
| 229 | + // Check that is filled with 0 |
| 230 | + if let ExprKind::Lit(ref lit) = fill_arg.node; |
| 231 | + if let LitKind::Int(0, _) = lit.node; |
| 232 | + |
| 233 | + // Check that len expression is equals to `with_capacity` expression |
| 234 | + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_ini.len_expr); |
| 235 | + |
| 236 | + then { |
| 237 | + self.slow_expression = Some(InitializationType::Resize(expr)); |
| 238 | + } |
| 239 | + } |
| 240 | + } |
| 241 | + |
| 242 | + /// Returns `true` if give expression is `repeat(0).take(...)` |
| 243 | + fn is_repeat_take(&self, expr: &Expr) -> bool { |
| 244 | + if_chain! { |
| 245 | + if let ExprKind::MethodCall(ref take_path, _, ref take_args) = expr.node; |
| 246 | + if take_path.ident.name == "take"; |
| 247 | + |
| 248 | + // Check that take is applied to `repeat(0)` |
| 249 | + if let Some(ref repeat_expr) = take_args.get(0); |
| 250 | + if self.is_repeat_zero(repeat_expr); |
| 251 | + |
| 252 | + // Check that len expression is equals to `with_capacity` expression |
| 253 | + if let Some(ref len_arg) = take_args.get(1); |
| 254 | + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_ini.len_expr); |
| 255 | + |
| 256 | + then { |
| 257 | + return true; |
| 258 | + } |
| 259 | + } |
| 260 | + |
| 261 | + false |
| 262 | + } |
| 263 | + |
| 264 | + /// Returns `true` if given expression is `repeat(0)` |
| 265 | + fn is_repeat_zero(&self, expr: &Expr) -> bool { |
| 266 | + if_chain! { |
| 267 | + if let ExprKind::Call(ref fn_expr, ref repeat_args) = expr.node; |
| 268 | + if let ExprKind::Path(ref qpath_repeat) = fn_expr.node; |
| 269 | + if match_qpath(&qpath_repeat, &["repeat"]); |
| 270 | + if let Some(ref repeat_arg) = repeat_args.get(0); |
| 271 | + if let ExprKind::Lit(ref lit) = repeat_arg.node; |
| 272 | + if let LitKind::Int(0, _) = lit.node; |
| 273 | + |
| 274 | + then { |
| 275 | + return true |
| 276 | + } |
| 277 | + } |
| 278 | + |
| 279 | + false |
| 280 | + } |
| 281 | +} |
| 282 | + |
| 283 | +impl<'a, 'tcx> Visitor<'tcx> for SlowInitializationVisitor<'a, 'tcx> { |
| 284 | + fn visit_expr(&mut self, expr: &'tcx Expr) { |
| 285 | + // Stop the search if we already found a slow zero-filling initialization |
| 286 | + if self.slow_expression.is_some() { |
| 287 | + return |
| 288 | + } |
| 289 | + |
| 290 | + // Skip all the expressions previous to the vector initialization |
| 291 | + if self.vec_ini.initialization_expr.id == expr.id { |
| 292 | + self.initialization_found = true; |
| 293 | + } |
| 294 | + |
| 295 | + self.search_slow_extend_filling(expr); |
| 296 | + self.search_slow_resize_filling(expr); |
| 297 | + |
| 298 | + walk_expr(self, expr); |
| 299 | + } |
| 300 | + |
| 301 | + fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> { |
| 302 | + NestedVisitorMap::None |
| 303 | + } |
| 304 | +} |
0 commit comments