Skip to content

Commit dd9c8d3

Browse files
committed
Extract get_vec_init_kind and share it
1 parent fec20bf commit dd9c8d3

File tree

4 files changed

+105
-112
lines changed

4 files changed

+105
-112
lines changed

clippy_lints/src/uninit_vec.rs

Lines changed: 52 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,50 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::get_vec_init_kind;
23
use clippy_utils::ty::is_type_diagnostic_item;
3-
use clippy_utils::{
4-
match_def_path, path_to_local_id, paths, peel_hir_expr_while, ty::is_uninit_value_valid_for_ty, SpanlessEq,
5-
};
6-
use rustc_hir::def::Res;
7-
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, Stmt, StmtKind};
4+
use clippy_utils::{path_to_local_id, peel_hir_expr_while, ty::is_uninit_value_valid_for_ty, SpanlessEq};
5+
use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind};
86
use rustc_lint::{LateContext, LateLintPass};
97
use rustc_middle::ty;
108
use rustc_session::{declare_lint_pass, declare_tool_lint};
119
use rustc_span::{sym, Span};
1210

11+
// TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
1312
declare_clippy_lint! {
1413
/// ### What it does
15-
/// Checks for the creation of uninitialized `Vec<T>` by calling `set_len()`
16-
/// immediately after `with_capacity()` or `reserve()`.
14+
/// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
15+
/// This is commonly caused by calling `set_len()` right after after calling
16+
/// `with_capacity()` or `reserve()`.
1717
///
1818
/// ### Why is this bad?
19-
/// It creates `Vec<T>` that contains uninitialized data, which leads to an
19+
/// It creates a `Vec` with uninitialized data, which leads to an
2020
/// undefined behavior with most safe operations.
21-
/// Notably, using uninitialized `Vec<u8>` with generic `Read` is unsound.
21+
/// Notably, uninitialized `Vec<u8>` must not be used with generic `Read`.
2222
///
2323
/// ### Example
2424
/// ```rust,ignore
2525
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
2626
/// unsafe { vec.set_len(1000); }
2727
/// reader.read(&mut vec); // undefined behavior!
2828
/// ```
29-
/// Use an initialized buffer:
30-
/// ```rust,ignore
31-
/// let mut vec: Vec<u8> = vec![0; 1000];
32-
/// reader.read(&mut vec);
33-
/// ```
34-
/// Or, wrap the content in `MaybeUninit`:
35-
/// ```rust,ignore
36-
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
37-
/// unsafe { vec.set_len(1000); }
38-
/// ```
29+
///
30+
/// ### How to fix?
31+
/// 1. Use an initialized buffer:
32+
/// ```rust,ignore
33+
/// let mut vec: Vec<u8> = vec![0; 1000];
34+
/// reader.read(&mut vec);
35+
/// ```
36+
/// 2. Wrap the content in `MaybeUninit`:
37+
/// ```rust,ignore
38+
/// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
39+
/// vec.set_len(1000); // `MaybeUninit` can be uninitialized
40+
/// ```
41+
/// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available:
42+
/// ```rust,ignore
43+
/// let mut vec: Vec<u8> = Vec::with_capacity(1000);
44+
/// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
45+
/// // perform initialization with `remaining`
46+
/// vec.set_len(...); // Safe to call `set_len()` on initialized part
47+
/// ```
3948
pub UNINIT_VEC,
4049
correctness,
4150
"Vec with uninitialized data"
@@ -59,24 +68,24 @@ impl<'tcx> LateLintPass<'tcx> for UninitVec {
5968

6069
fn handle_uninit_vec_pair(
6170
cx: &LateContext<'tcx>,
62-
maybe_with_capacity_or_reserve: &'tcx Stmt<'tcx>,
71+
maybe_init_or_reserve: &'tcx Stmt<'tcx>,
6372
maybe_set_len: &'tcx Expr<'tcx>,
6473
) {
6574
if_chain! {
66-
if let Some(vec) = extract_with_capacity_or_reserve_target(cx, maybe_with_capacity_or_reserve);
75+
if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve);
6776
if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len);
6877
if vec.eq_expr(cx, set_len_self);
6978
if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind();
7079
if let ty::Adt(_, substs) = vec_ty.kind();
7180
// Check T of Vec<T>
7281
if !is_uninit_value_valid_for_ty(cx, substs.type_at(0));
7382
then {
74-
// FIXME: false positive #7698
83+
// FIXME: #7698, false positive of the internal lints
7584
#[allow(clippy::collapsible_span_lint_calls)]
7685
span_lint_and_then(
7786
cx,
7887
UNINIT_VEC,
79-
vec![call_span, maybe_with_capacity_or_reserve.span],
88+
vec![call_span, maybe_init_or_reserve.span],
8089
"calling `set_len()` immediately after reserving a buffer creates uninitialized values",
8190
|diag| {
8291
diag.help("initialize the buffer or wrap the content in `MaybeUninit`");
@@ -101,56 +110,36 @@ impl<'tcx> LocalOrExpr<'tcx> {
101110
}
102111
}
103112

104-
/// Returns the target vec of `Vec::with_capacity()` or `Vec::reserve()`
105-
fn extract_with_capacity_or_reserve_target(cx: &LateContext<'_>, stmt: &'tcx Stmt<'_>) -> Option<LocalOrExpr<'tcx>> {
113+
/// Finds the target location where the result of `Vec` initialization is stored
114+
/// or `self` expression for `Vec::reserve()`.
115+
fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<LocalOrExpr<'tcx>> {
106116
match stmt.kind {
107117
StmtKind::Local(local) => {
108-
// let mut x = Vec::with_capacity()
109118
if_chain! {
110119
if let Some(init_expr) = local.init;
111120
if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind;
112-
if is_with_capacity(cx, init_expr);
121+
if get_vec_init_kind(cx, init_expr).is_some();
113122
then {
114123
Some(LocalOrExpr::Local(hir_id))
115124
} else {
116125
None
117126
}
118127
}
119128
},
120-
StmtKind::Expr(expr) | StmtKind::Semi(expr) => {
121-
match expr.kind {
122-
ExprKind::Assign(lhs, rhs, _span) if is_with_capacity(cx, rhs) => {
123-
// self.vec = Vec::with_capacity()
124-
Some(LocalOrExpr::Expr(lhs))
125-
},
126-
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
127-
// self.vec.reserve()
128-
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::vec_type)
129-
&& path.ident.name.as_str() == "reserve"
130-
{
131-
Some(LocalOrExpr::Expr(self_expr))
132-
} else {
133-
None
134-
}
135-
},
136-
_ => None,
137-
}
129+
StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind {
130+
ExprKind::Assign(lhs, rhs, _span) if get_vec_init_kind(cx, rhs).is_some() => Some(LocalOrExpr::Expr(lhs)),
131+
ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => {
132+
Some(LocalOrExpr::Expr(self_expr))
133+
},
134+
_ => None,
138135
},
139136
StmtKind::Item(_) => None,
140137
}
141138
}
142139

143-
fn is_with_capacity(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> bool {
144-
if_chain! {
145-
if let ExprKind::Call(path_expr, _) = &expr.kind;
146-
if let ExprKind::Path(qpath) = &path_expr.kind;
147-
if let Res::Def(_, def_id) = cx.qpath_res(qpath, path_expr.hir_id);
148-
then {
149-
match_def_path(cx, def_id, &paths::VEC_WITH_CAPACITY)
150-
} else {
151-
false
152-
}
153-
}
140+
fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool {
141+
is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec)
142+
&& path.ident.name.as_str() == "reserve"
154143
}
155144

156145
/// Returns self if the expression is `Vec::set_len()`
@@ -169,14 +158,13 @@ fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&
169158
}
170159
});
171160
match expr.kind {
172-
ExprKind::MethodCall(_, _, [vec_expr, _], _) => {
173-
cx.typeck_results().type_dependent_def_id(expr.hir_id).and_then(|id| {
174-
if match_def_path(cx, id, &paths::VEC_SET_LEN) {
175-
Some((vec_expr, expr.span))
176-
} else {
177-
None
178-
}
179-
})
161+
ExprKind::MethodCall(path, _, [self_expr, _], _) => {
162+
let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs();
163+
if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" {
164+
Some((self_expr, expr.span))
165+
} else {
166+
None
167+
}
180168
},
181169
_ => None,
182170
}

clippy_lints/src/vec_init_then_push.rs

Lines changed: 5 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet;
3-
use clippy_utils::ty::is_type_diagnostic_item;
4-
use clippy_utils::{match_def_path, path_to_local, path_to_local_id, paths};
3+
use clippy_utils::{get_vec_init_kind, path_to_local, path_to_local_id, VecInitKind};
54
use if_chain::if_chain;
6-
use rustc_ast::ast::LitKind;
75
use rustc_errors::Applicability;
8-
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, QPath, Stmt, StmtKind};
6+
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Local, PatKind, Stmt, StmtKind};
97
use rustc_lint::{LateContext, LateLintPass, LintContext};
108
use rustc_middle::lint::in_external_macro;
119
use rustc_session::{declare_tool_lint, impl_lint_pass};
12-
use rustc_span::{symbol::sym, Span};
13-
use std::convert::TryInto;
10+
use rustc_span::Span;
1411

1512
declare_clippy_lint! {
1613
/// ### What it does
@@ -41,11 +38,6 @@ pub struct VecInitThenPush {
4138
searcher: Option<VecPushSearcher>,
4239
}
4340

44-
#[derive(Clone, Copy)]
45-
enum VecInitKind {
46-
New,
47-
WithCapacity(u64),
48-
}
4941
struct VecPushSearcher {
5042
local_id: HirId,
5143
init: VecInitKind,
@@ -58,7 +50,8 @@ impl VecPushSearcher {
5850
fn display_err(&self, cx: &LateContext<'_>) {
5951
match self.init {
6052
_ if self.found == 0 => return,
61-
VecInitKind::WithCapacity(x) if x > self.found => return,
53+
VecInitKind::WithLiteralCapacity(x) if x > self.found => return,
54+
VecInitKind::WithExprCapacity(_) => return,
6255
_ => (),
6356
};
6457

@@ -152,37 +145,3 @@ impl LateLintPass<'_> for VecInitThenPush {
152145
}
153146
}
154147
}
155-
156-
fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
157-
if let ExprKind::Call(func, args) = expr.kind {
158-
match func.kind {
159-
ExprKind::Path(QPath::TypeRelative(ty, name))
160-
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
161-
{
162-
if name.ident.name == sym::new {
163-
return Some(VecInitKind::New);
164-
} else if name.ident.name.as_str() == "with_capacity" {
165-
return args.get(0).and_then(|arg| {
166-
if_chain! {
167-
if let ExprKind::Lit(lit) = &arg.kind;
168-
if let LitKind::Int(num, _) = lit.node;
169-
then {
170-
Some(VecInitKind::WithCapacity(num.try_into().ok()?))
171-
} else {
172-
None
173-
}
174-
}
175-
});
176-
}
177-
}
178-
ExprKind::Path(QPath::Resolved(_, path))
179-
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
180-
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
181-
{
182-
return Some(VecInitKind::New);
183-
}
184-
_ => (),
185-
}
186-
}
187-
None
188-
}

clippy_utils/src/lib.rs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ use rustc_span::{Span, DUMMY_SP};
9393
use rustc_target::abi::Integer;
9494

9595
use crate::consts::{constant, Constant};
96-
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type};
96+
use crate::ty::{can_partially_move_ty, is_copy, is_recursively_primitive_type, is_type_diagnostic_item};
9797

9898
pub fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option<Span>) -> Option<RustcVersion> {
9999
if let Ok(version) = RustcVersion::parse(msrv) {
@@ -1789,6 +1789,53 @@ pub fn is_expr_identity_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool
17891789
}
17901790
}
17911791

1792+
#[derive(Clone, Copy)]
1793+
pub enum VecInitKind {
1794+
/// `Vec::new()`
1795+
New,
1796+
/// `Vec::default()` or `Default::default()`
1797+
Default,
1798+
/// `Vec::with_capacity(123)`
1799+
WithLiteralCapacity(u64),
1800+
/// `Vec::with_capacity(slice.len())`
1801+
WithExprCapacity(HirId),
1802+
}
1803+
1804+
/// Checks if given expression is an initialization of `Vec` and returns its kind.
1805+
pub fn get_vec_init_kind<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<VecInitKind> {
1806+
if let ExprKind::Call(func, args) = expr.kind {
1807+
match func.kind {
1808+
ExprKind::Path(QPath::TypeRelative(ty, name))
1809+
if is_type_diagnostic_item(cx, cx.typeck_results().node_type(ty.hir_id), sym::Vec) =>
1810+
{
1811+
if name.ident.name == sym::new {
1812+
return Some(VecInitKind::New);
1813+
} else if name.ident.name.as_str() == "with_capacity" {
1814+
return args.get(0).and_then(|arg| {
1815+
if_chain! {
1816+
if let ExprKind::Lit(lit) = &arg.kind;
1817+
if let LitKind::Int(num, _) = lit.node;
1818+
then {
1819+
Some(VecInitKind::WithLiteralCapacity(num.try_into().ok()?))
1820+
} else {
1821+
Some(VecInitKind::WithExprCapacity(arg.hir_id))
1822+
}
1823+
}
1824+
});
1825+
}
1826+
}
1827+
ExprKind::Path(QPath::Resolved(_, path))
1828+
if match_def_path(cx, path.res.opt_def_id()?, &paths::DEFAULT_TRAIT_METHOD)
1829+
&& is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(expr), sym::Vec) =>
1830+
{
1831+
return Some(VecInitKind::Default);
1832+
}
1833+
_ => (),
1834+
}
1835+
}
1836+
None
1837+
}
1838+
17921839
/// Gets the node where an expression is either used, or it's type is unified with another branch.
17931840
pub fn get_expr_use_or_unification_node(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<Node<'tcx>> {
17941841
let mut child_id = expr.hir_id;

clippy_utils/src/paths.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,6 @@ pub const VEC_AS_SLICE: [&str; 4] = ["alloc", "vec", "Vec", "as_slice"];
183183
pub const VEC_FROM_ELEM: [&str; 3] = ["alloc", "vec", "from_elem"];
184184
pub const VEC_NEW: [&str; 4] = ["alloc", "vec", "Vec", "new"];
185185
pub const VEC_RESIZE: [&str; 4] = ["alloc", "vec", "Vec", "resize"];
186-
pub const VEC_WITH_CAPACITY: [&str; 4] = ["alloc", "vec", "Vec", "with_capacity"];
187186
pub const VEC_SET_LEN: [&str; 4] = ["alloc", "vec", "Vec", "set_len"];
188187
pub const WEAK_ARC: [&str; 3] = ["alloc", "sync", "Weak"];
189188
pub const WEAK_RC: [&str; 3] = ["alloc", "rc", "Weak"];

0 commit comments

Comments
 (0)