1 use clippy_utils
::diagnostics
::{span_lint, span_lint_and_then}
;
2 use clippy_utils
::higher
::{get_vec_init_kind, VecInitKind}
;
3 use clippy_utils
::ty
::{is_type_diagnostic_item, is_uninit_value_valid_for_ty}
;
4 use clippy_utils
::{is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq}
;
5 use rustc_hir
::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind}
;
6 use rustc_lint
::{LateContext, LateLintPass}
;
7 use rustc_middle
::lint
::in_external_macro
;
9 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
10 use rustc_span
::{sym, Span}
;
12 // TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std
13 declare_clippy_lint
! {
15 /// Checks for `set_len()` call that creates `Vec` with uninitialized elements.
16 /// This is commonly caused by calling `set_len()` right after allocating or
17 /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`.
19 /// ### Why is this bad?
20 /// It creates a `Vec` with uninitialized data, which leads to
21 /// undefined behavior with most safe operations. Notably, uninitialized
22 /// `Vec<u8>` must not be used with generic `Read`.
24 /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()`
25 /// creates out-of-bound values that lead to heap memory corruption when used.
27 /// ### Known Problems
28 /// This lint only checks directly adjacent statements.
32 /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
33 /// unsafe { vec.set_len(1000); }
34 /// reader.read(&mut vec); // undefined behavior!
38 /// 1. Use an initialized buffer:
40 /// let mut vec: Vec<u8> = vec![0; 1000];
41 /// reader.read(&mut vec);
43 /// 2. Wrap the content in `MaybeUninit`:
45 /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000);
46 /// vec.set_len(1000); // `MaybeUninit` can be uninitialized
48 /// 3. If you are on 1.60.0 or later, `Vec::spare_capacity_mut()` is available:
50 /// let mut vec: Vec<u8> = Vec::with_capacity(1000);
51 /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]`
52 /// // perform initialization with `remaining`
53 /// vec.set_len(...); // Safe to call `set_len()` on initialized part
55 #[clippy::version = "1.58.0"]
58 "Vec with uninitialized data"
61 declare_lint_pass
!(UninitVec
=> [UNINIT_VEC
]);
63 // FIXME: update to a visitor-based implementation.
64 // Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368
65 impl<'tcx
> LateLintPass
<'tcx
> for UninitVec
{
66 fn check_block(&mut self, cx
: &LateContext
<'tcx
>, block
: &'tcx Block
<'_
>) {
67 if !in_external_macro(cx
.tcx
.sess
, block
.span
) {
68 for w
in block
.stmts
.windows(2) {
69 if let StmtKind
::Expr(expr
) | StmtKind
::Semi(expr
) = w
[1].kind
{
70 handle_uninit_vec_pair(cx
, &w
[0], expr
);
74 if let (Some(stmt
), Some(expr
)) = (block
.stmts
.last(), block
.expr
) {
75 handle_uninit_vec_pair(cx
, stmt
, expr
);
81 fn handle_uninit_vec_pair
<'tcx
>(
82 cx
: &LateContext
<'tcx
>,
83 maybe_init_or_reserve
: &'tcx Stmt
<'tcx
>,
84 maybe_set_len
: &'tcx Expr
<'tcx
>,
87 if let Some(vec
) = extract_init_or_reserve_target(cx
, maybe_init_or_reserve
);
88 if let Some((set_len_self
, call_span
)) = extract_set_len_self(cx
, maybe_set_len
);
89 if vec
.location
.eq_expr(cx
, set_len_self
);
90 if let ty
::Ref(_
, vec_ty
, _
) = cx
.typeck_results().expr_ty_adjusted(set_len_self
).kind();
91 if let ty
::Adt(_
, substs
) = vec_ty
.kind();
92 // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()`
93 if !is_lint_allowed(cx
, UNINIT_VEC
, maybe_set_len
.hir_id
);
95 if vec
.has_capacity() {
96 // with_capacity / reserve -> set_len
99 if !is_uninit_value_valid_for_ty(cx
, substs
.type_at(0)) {
100 // FIXME: #7698, false positive of the internal lints
101 #[expect(clippy::collapsible_span_lint_calls)]
105 vec
![call_span
, maybe_init_or_reserve
.span
],
106 "calling `set_len()` immediately after reserving a buffer creates uninitialized values",
108 diag
.help("initialize the buffer or wrap the content in `MaybeUninit`");
113 // new / default -> set_len
117 vec
![call_span
, maybe_init_or_reserve
.span
],
118 "calling `set_len()` on empty `Vec` creates out-of-bound values",
125 /// The target `Vec` that is initialized or reserved
126 #[derive(Clone, Copy)]
127 struct TargetVec
<'tcx
> {
128 location
: VecLocation
<'tcx
>,
129 /// `None` if `reserve()`
130 init_kind
: Option
<VecInitKind
>,
134 pub fn has_capacity(self) -> bool
{
135 !matches
!(self.init_kind
, Some(VecInitKind
::New
| VecInitKind
::Default
))
139 #[derive(Clone, Copy)]
140 enum VecLocation
<'tcx
> {
142 Expr(&'tcx Expr
<'tcx
>),
145 impl<'tcx
> VecLocation
<'tcx
> {
146 pub fn eq_expr(self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>) -> bool
{
148 VecLocation
::Local(hir_id
) => path_to_local_id(expr
, hir_id
),
149 VecLocation
::Expr(self_expr
) => SpanlessEq
::new(cx
).eq_expr(self_expr
, expr
),
154 /// Finds the target location where the result of `Vec` initialization is stored
155 /// or `self` expression for `Vec::reserve()`.
156 fn extract_init_or_reserve_target
<'tcx
>(cx
: &LateContext
<'tcx
>, stmt
: &'tcx Stmt
<'tcx
>) -> Option
<TargetVec
<'tcx
>> {
158 StmtKind
::Local(local
) => {
160 if let Some(init_expr
) = local
.init
;
161 if let PatKind
::Binding(_
, hir_id
, _
, None
) = local
.pat
.kind
;
162 if let Some(init_kind
) = get_vec_init_kind(cx
, init_expr
);
164 return Some(TargetVec
{
165 location
: VecLocation
::Local(hir_id
),
166 init_kind
: Some(init_kind
),
171 StmtKind
::Expr(expr
) | StmtKind
::Semi(expr
) => match expr
.kind
{
172 ExprKind
::Assign(lhs
, rhs
, _span
) => {
173 if let Some(init_kind
) = get_vec_init_kind(cx
, rhs
) {
174 return Some(TargetVec
{
175 location
: VecLocation
::Expr(lhs
),
176 init_kind
: Some(init_kind
),
180 ExprKind
::MethodCall(path
, self_expr
, [_
], _
) if is_reserve(cx
, path
, self_expr
) => {
181 return Some(TargetVec
{
182 location
: VecLocation
::Expr(self_expr
),
188 StmtKind
::Item(_
) => (),
193 fn is_reserve(cx
: &LateContext
<'_
>, path
: &PathSegment
<'_
>, self_expr
: &Expr
<'_
>) -> bool
{
194 is_type_diagnostic_item(cx
, cx
.typeck_results().expr_ty(self_expr
).peel_refs(), sym
::Vec
)
195 && path
.ident
.name
.as_str() == "reserve"
198 /// Returns self if the expression is `Vec::set_len()`
199 fn extract_set_len_self
<'tcx
>(cx
: &LateContext
<'_
>, expr
: &'tcx Expr
<'_
>) -> Option
<(&'tcx Expr
<'tcx
>, Span
)> {
200 // peel unsafe blocks in `unsafe { vec.set_len() }`
201 let expr
= peel_hir_expr_while(expr
, |e
| {
202 if let ExprKind
::Block(block
, _
) = e
.kind
{
203 // Extract the first statement/expression
204 match (block
.stmts
.get(0).map(|stmt
| &stmt
.kind
), block
.expr
) {
205 (None
, Some(expr
)) => Some(expr
),
206 (Some(StmtKind
::Expr(expr
) | StmtKind
::Semi(expr
)), _
) => Some(expr
),
214 ExprKind
::MethodCall(path
, self_expr
, [arg
], _
) => {
215 let self_type
= cx
.typeck_results().expr_ty(self_expr
).peel_refs();
216 if is_type_diagnostic_item(cx
, self_type
, sym
::Vec
)
217 && path
.ident
.name
.as_str() == "set_len"
218 && !is_integer_literal(arg
, 0)
220 Some((self_expr
, expr
.span
))