Skip to content

Commit e0ccc9d

Browse files
committed
Add slow zero-filled vector initialization lint
Add lint to detect slow zero-filled vector initialization. It detects when a vector is zero-filled with extended with `repeat(0).take(len)` or `resize(len, 0)`. This zero-fillings are usually slower than simply using `vec![0; len]`.
1 parent 69d09fb commit e0ccc9d

File tree

5 files changed

+471
-0
lines changed

5 files changed

+471
-0
lines changed

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ pub mod replace_consts;
190190
pub mod returns;
191191
pub mod serde_api;
192192
pub mod shadow;
193+
pub mod slow_vector_initialization;
193194
pub mod strings;
194195
pub mod suspicious_trait_impl;
195196
pub mod swap;
@@ -459,6 +460,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
459460
reg.register_late_lint_pass(box non_copy_const::NonCopyConst);
460461
reg.register_late_lint_pass(box ptr_offset_with_cast::Pass);
461462
reg.register_late_lint_pass(box redundant_clone::RedundantClone);
463+
reg.register_late_lint_pass(box slow_vector_initialization::Pass);
462464

463465
reg.register_lint_group("clippy::restriction", Some("clippy_restriction"), vec![
464466
arithmetic::FLOAT_ARITHMETIC,
@@ -980,6 +982,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry<'_>, conf: &Conf) {
980982
methods::SINGLE_CHAR_PATTERN,
981983
misc::CMP_OWNED,
982984
mutex_atomic::MUTEX_ATOMIC,
985+
slow_vector_initialization::SLOW_VECTOR_INITIALIZATION,
983986
trivially_copy_pass_by_ref::TRIVIALLY_COPY_PASS_BY_REF,
984987
types::BOX_VEC,
985988
vec::USELESS_VEC,
Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
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+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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 std::iter::repeat;
11+
12+
fn main() {
13+
resize_vector();
14+
extend_vector();
15+
mixed_extend_resize_vector();
16+
}
17+
18+
fn extend_vector() {
19+
// Extend with constant expression
20+
let len = 300;
21+
let mut vec1 = Vec::with_capacity(len);
22+
vec1.extend(repeat(0).take(len));
23+
24+
// Extend with len expression
25+
let mut vec2 = Vec::with_capacity(len - 10);
26+
vec2.extend(repeat(0).take(len - 10));
27+
28+
// Extend with mismatching expression should not be warned
29+
let mut vec3 = Vec::with_capacity(24322);
30+
vec3.extend(repeat(0).take(2));
31+
}
32+
33+
fn mixed_extend_resize_vector() {
34+
// Mismatching len
35+
let mut mismatching_len = Vec::with_capacity(30);
36+
37+
// Slow initialization
38+
let mut resized_vec = Vec::with_capacity(30);
39+
let mut extend_vec = Vec::with_capacity(30);
40+
41+
resized_vec.resize(30, 0);
42+
mismatching_len.extend(repeat(0).take(40));
43+
extend_vec.extend(repeat(0).take(30));
44+
}
45+
46+
fn resize_vector() {
47+
// Resize with constant expression
48+
let len = 300;
49+
let mut vec1 = Vec::with_capacity(len);
50+
vec1.resize(len, 0);
51+
52+
// Resize mismatch len
53+
let mut vec2 = Vec::with_capacity(200);
54+
vec2.resize(10, 0);
55+
56+
// Resize with len expression
57+
let mut vec3 = Vec::with_capacity(len - 10);
58+
vec3.resize(len - 10, 0);
59+
60+
// Reinitialization should be warned
61+
vec1 = Vec::with_capacity(10);
62+
vec1.resize(10, 0);
63+
}

0 commit comments

Comments
 (0)