Skip to content

Commit 70c5798

Browse files
committed
Auto merge of #11198 - y21:issue10938, r=Centri3
[`slow_vector_initialization`]: catch `Vec::new()` followed by `.resize(len, 0)` Closes #10938 changelog: [`slow_vector_initialization`]: catch `Vec::new()` followed by `.resize(len, 0)`
2 parents d09c8a9 + c0484b7 commit 70c5798

File tree

6 files changed

+148
-72
lines changed

6 files changed

+148
-72
lines changed

clippy_lints/src/slow_vector_initialization.rs

+72-41
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::sugg::Sugg;
3-
use clippy_utils::ty::is_type_diagnostic_item;
43
use clippy_utils::{
5-
get_enclosing_block, is_integer_literal, is_path_diagnostic_item, path_to_local, path_to_local_id, SpanlessEq,
4+
get_enclosing_block, is_expr_path_def_path, is_integer_literal, is_path_diagnostic_item, path_to_local,
5+
path_to_local_id, paths, SpanlessEq,
66
};
77
use if_chain::if_chain;
88
use rustc_errors::Applicability;
99
use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, Visitor};
10-
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind};
10+
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind};
1111
use rustc_lint::{LateContext, LateLintPass};
1212
use rustc_session::{declare_lint_pass, declare_tool_lint};
1313
use rustc_span::symbol::sym;
@@ -60,7 +60,24 @@ struct VecAllocation<'tcx> {
6060

6161
/// Reference to the expression used as argument on `with_capacity` call. This is used
6262
/// to only match slow zero-filling idioms of the same length than vector initialization.
63-
len_expr: &'tcx Expr<'tcx>,
63+
size_expr: InitializedSize<'tcx>,
64+
}
65+
66+
/// Initializer for the creation of the vector.
67+
///
68+
/// When `Vec::with_capacity(size)` is found, the `size` expression will be in
69+
/// `InitializedSize::Initialized`.
70+
///
71+
/// Otherwise, for `Vec::new()` calls, there is no allocation initializer yet, so
72+
/// `InitializedSize::Uninitialized` is used.
73+
/// Later, when a call to `.resize(size, 0)` or similar is found, it's set
74+
/// to `InitializedSize::Initialized(size)`.
75+
///
76+
/// Since it will be set to `InitializedSize::Initialized(size)` when a slow initialization is
77+
/// found, it is always safe to "unwrap" it at lint time.
78+
enum InitializedSize<'tcx> {
79+
Initialized(&'tcx Expr<'tcx>),
80+
Uninitialized,
6481
}
6582

6683
/// Type of slow initialization
@@ -77,18 +94,14 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
7794
// Matches initialization on reassignments. For example: `vec = Vec::with_capacity(100)`
7895
if_chain! {
7996
if let ExprKind::Assign(left, right, _) = expr.kind;
80-
81-
// Extract variable
8297
if let Some(local_id) = path_to_local(left);
83-
84-
// Extract len argument
85-
if let Some(len_arg) = Self::is_vec_with_capacity(cx, right);
98+
if let Some(size_expr) = Self::as_vec_initializer(cx, right);
8699

87100
then {
88101
let vi = VecAllocation {
89102
local_id,
90103
allocation_expr: right,
91-
len_expr: len_arg,
104+
size_expr,
92105
};
93106

94107
Self::search_initialization(cx, vi, expr.hir_id);
@@ -98,17 +111,18 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
98111

99112
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) {
100113
// Matches statements which initializes vectors. For example: `let mut vec = Vec::with_capacity(10)`
114+
// or `Vec::new()`
101115
if_chain! {
102116
if let StmtKind::Local(local) = stmt.kind;
103117
if let PatKind::Binding(BindingAnnotation::MUT, local_id, _, None) = local.pat.kind;
104118
if let Some(init) = local.init;
105-
if let Some(len_arg) = Self::is_vec_with_capacity(cx, init);
119+
if let Some(size_expr) = Self::as_vec_initializer(cx, init);
106120

107121
then {
108122
let vi = VecAllocation {
109123
local_id,
110124
allocation_expr: init,
111-
len_expr: len_arg,
125+
size_expr,
112126
};
113127

114128
Self::search_initialization(cx, vi, stmt.hir_id);
@@ -118,19 +132,20 @@ impl<'tcx> LateLintPass<'tcx> for SlowVectorInit {
118132
}
119133

120134
impl SlowVectorInit {
121-
/// Checks if the given expression is `Vec::with_capacity(..)`. It will return the expression
122-
/// of the first argument of `with_capacity` call if it matches or `None` if it does not.
123-
fn is_vec_with_capacity<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> {
124-
if_chain! {
125-
if let ExprKind::Call(func, [arg]) = expr.kind;
126-
if let ExprKind::Path(QPath::TypeRelative(ty, name)) = func.kind;
127-
if name.ident.as_str() == "with_capacity";
128-
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec);
129-
then {
130-
Some(arg)
131-
} else {
132-
None
133-
}
135+
/// Looks for `Vec::with_capacity(size)` or `Vec::new()` calls and returns the initialized size,
136+
/// if any. More specifically, it returns:
137+
/// - `Some(InitializedSize::Initialized(size))` for `Vec::with_capacity(size)`
138+
/// - `Some(InitializedSize::Uninitialized)` for `Vec::new()`
139+
/// - `None` for other, unrelated kinds of expressions
140+
fn as_vec_initializer<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<InitializedSize<'tcx>> {
141+
if let ExprKind::Call(func, [len_expr]) = expr.kind
142+
&& is_expr_path_def_path(cx, func, &paths::VEC_WITH_CAPACITY)
143+
{
144+
Some(InitializedSize::Initialized(len_expr))
145+
} else if matches!(expr.kind, ExprKind::Call(func, _) if is_expr_path_def_path(cx, func, &paths::VEC_NEW)) {
146+
Some(InitializedSize::Uninitialized)
147+
} else {
148+
None
134149
}
135150
}
136151

@@ -169,12 +184,19 @@ impl SlowVectorInit {
169184
}
170185

171186
fn emit_lint(cx: &LateContext<'_>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) {
172-
let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len");
187+
let len_expr = Sugg::hir(
188+
cx,
189+
match vec_alloc.size_expr {
190+
InitializedSize::Initialized(expr) => expr,
191+
InitializedSize::Uninitialized => unreachable!("size expression must be set by this point"),
192+
},
193+
"len",
194+
);
173195

174196
span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| {
175197
diag.span_suggestion(
176198
vec_alloc.allocation_expr.span,
177-
"consider replace allocation with",
199+
"consider replacing this with",
178200
format!("vec![0; {len_expr}]"),
179201
Applicability::Unspecified,
180202
);
@@ -214,36 +236,45 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> {
214236
}
215237

216238
/// Checks if the given expression is resizing a vector with 0
217-
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) {
239+
fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'tcx>) {
218240
if self.initialization_found
219241
&& let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind
220242
&& path_to_local_id(self_arg, self.vec_alloc.local_id)
221243
&& path.ident.name == sym!(resize)
222244
// Check that is filled with 0
223-
&& is_integer_literal(fill_arg, 0) {
224-
// Check that len expression is equals to `with_capacity` expression
225-
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
226-
self.slow_expression = Some(InitializationType::Resize(expr));
227-
} else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" {
228-
self.slow_expression = Some(InitializationType::Resize(expr));
229-
}
245+
&& is_integer_literal(fill_arg, 0)
246+
{
247+
let is_matching_resize = if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
248+
// If we have a size expression, check that it is equal to what's passed to `resize`
249+
SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
250+
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity")
251+
} else {
252+
self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg);
253+
true
254+
};
255+
256+
if is_matching_resize {
257+
self.slow_expression = Some(InitializationType::Resize(expr));
230258
}
259+
}
231260
}
232261

233262
/// Returns `true` if give expression is `repeat(0).take(...)`
234-
fn is_repeat_take(&self, expr: &Expr<'_>) -> bool {
263+
fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool {
235264
if_chain! {
236265
if let ExprKind::MethodCall(take_path, recv, [len_arg, ..], _) = expr.kind;
237266
if take_path.ident.name == sym!(take);
238267
// Check that take is applied to `repeat(0)`
239268
if self.is_repeat_zero(recv);
240269
then {
241-
// Check that len expression is equals to `with_capacity` expression
242-
if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) {
243-
return true;
244-
} else if let ExprKind::MethodCall(path, ..) = len_arg.kind && path.ident.as_str() == "capacity" {
245-
return true;
270+
if let InitializedSize::Initialized(size_expr) = self.vec_alloc.size_expr {
271+
// Check that len expression is equals to `with_capacity` expression
272+
return SpanlessEq::new(self.cx).eq_expr(len_arg, size_expr)
273+
|| matches!(len_arg.kind, ExprKind::MethodCall(path, ..) if path.ident.as_str() == "capacity")
246274
}
275+
276+
self.vec_alloc.size_expr = InitializedSize::Initialized(len_arg);
277+
return true;
247278
}
248279
}
249280

clippy_utils/src/paths.rs

+1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
149149
pub const VEC_DEQUE_ITER: [&str; 5] = ["alloc", "collections", "vec_deque", "VecDeque", "iter"];
150150
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
151151
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
152+
pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"];
152153
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
153154
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
154155
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];

tests/ui/read_zero_byte_vec.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
#![warn(clippy::read_zero_byte_vec)]
2-
#![allow(clippy::unused_io_amount, clippy::needless_pass_by_ref_mut)]
2+
#![allow(
3+
clippy::unused_io_amount,
4+
clippy::needless_pass_by_ref_mut,
5+
clippy::slow_vector_initialization
6+
)]
37
use std::fs::File;
48
use std::io;
59
use std::io::prelude::*;

tests/ui/read_zero_byte_vec.stderr

+10-10
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,61 @@
11
error: reading zero byte data to `Vec`
2-
--> $DIR/read_zero_byte_vec.rs:17:5
2+
--> $DIR/read_zero_byte_vec.rs:21:5
33
|
44
LL | f.read_exact(&mut data).unwrap();
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();`
66
|
77
= note: `-D clippy::read-zero-byte-vec` implied by `-D warnings`
88

99
error: reading zero byte data to `Vec`
10-
--> $DIR/read_zero_byte_vec.rs:21:5
10+
--> $DIR/read_zero_byte_vec.rs:25:5
1111
|
1212
LL | f.read_exact(&mut data2)?;
1313
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;`
1414

1515
error: reading zero byte data to `Vec`
16-
--> $DIR/read_zero_byte_vec.rs:25:5
16+
--> $DIR/read_zero_byte_vec.rs:29:5
1717
|
1818
LL | f.read_exact(&mut data3)?;
1919
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
2020

2121
error: reading zero byte data to `Vec`
22-
--> $DIR/read_zero_byte_vec.rs:29:5
22+
--> $DIR/read_zero_byte_vec.rs:33:5
2323
|
2424
LL | let _ = f.read(&mut data4)?;
2525
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2626

2727
error: reading zero byte data to `Vec`
28-
--> $DIR/read_zero_byte_vec.rs:34:9
28+
--> $DIR/read_zero_byte_vec.rs:38:9
2929
|
3030
LL | f.read(&mut data5)
3131
| ^^^^^^^^^^^^^^^^^^
3232

3333
error: reading zero byte data to `Vec`
34-
--> $DIR/read_zero_byte_vec.rs:40:9
34+
--> $DIR/read_zero_byte_vec.rs:44:9
3535
|
3636
LL | f.read(&mut data6)
3737
| ^^^^^^^^^^^^^^^^^^
3838

3939
error: reading zero byte data to `Vec`
40-
--> $DIR/read_zero_byte_vec.rs:70:5
40+
--> $DIR/read_zero_byte_vec.rs:74:5
4141
|
4242
LL | r.read(&mut data).await.unwrap();
4343
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4444

4545
error: reading zero byte data to `Vec`
46-
--> $DIR/read_zero_byte_vec.rs:74:5
46+
--> $DIR/read_zero_byte_vec.rs:78:5
4747
|
4848
LL | r.read_exact(&mut data2).await.unwrap();
4949
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5050

5151
error: reading zero byte data to `Vec`
52-
--> $DIR/read_zero_byte_vec.rs:80:5
52+
--> $DIR/read_zero_byte_vec.rs:84:5
5353
|
5454
LL | r.read(&mut data).await.unwrap();
5555
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
5656

5757
error: reading zero byte data to `Vec`
58-
--> $DIR/read_zero_byte_vec.rs:84:5
58+
--> $DIR/read_zero_byte_vec.rs:88:5
5959
|
6060
LL | r.read_exact(&mut data2).await.unwrap();
6161
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

tests/ui/slow_vector_initialization.rs

+16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ fn main() {
44
resize_vector();
55
extend_vector();
66
mixed_extend_resize_vector();
7+
from_empty_vec();
78
}
89

910
fn extend_vector() {
@@ -59,6 +60,21 @@ fn resize_vector() {
5960
vec1.resize(10, 0);
6061
}
6162

63+
fn from_empty_vec() {
64+
// Resize with constant expression
65+
let len = 300;
66+
let mut vec1 = Vec::new();
67+
vec1.resize(len, 0);
68+
69+
// Resize with len expression
70+
let mut vec3 = Vec::new();
71+
vec3.resize(len - 10, 0);
72+
73+
// Reinitialization should be warned
74+
vec1 = Vec::new();
75+
vec1.resize(10, 0);
76+
}
77+
6278
fn do_stuff(vec: &mut [u8]) {}
6379

6480
fn extend_vector_with_manipulations_between() {

0 commit comments

Comments
 (0)