]>
Commit | Line | Data |
---|---|---|
3c0e092e XL |
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}; | |
2b03887a | 4 | use clippy_utils::{is_integer_literal, is_lint_allowed, path_to_local_id, peel_hir_expr_while, SpanlessEq}; |
3c0e092e XL |
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; | |
8 | use rustc_middle::ty; | |
9 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
10 | use rustc_span::{sym, Span}; | |
11 | ||
12 | // TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std | |
13 | declare_clippy_lint! { | |
14 | /// ### What it does | |
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()`. | |
18 | /// | |
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`. | |
23 | /// | |
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. | |
26 | /// | |
27 | /// ### Known Problems | |
28 | /// This lint only checks directly adjacent statements. | |
29 | /// | |
30 | /// ### Example | |
31 | /// ```rust,ignore | |
32 | /// let mut vec: Vec<u8> = Vec::with_capacity(1000); | |
33 | /// unsafe { vec.set_len(1000); } | |
34 | /// reader.read(&mut vec); // undefined behavior! | |
35 | /// ``` | |
36 | /// | |
37 | /// ### How to fix? | |
38 | /// 1. Use an initialized buffer: | |
39 | /// ```rust,ignore | |
40 | /// let mut vec: Vec<u8> = vec![0; 1000]; | |
41 | /// reader.read(&mut vec); | |
42 | /// ``` | |
43 | /// 2. Wrap the content in `MaybeUninit`: | |
44 | /// ```rust,ignore | |
45 | /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000); | |
46 | /// vec.set_len(1000); // `MaybeUninit` can be uninitialized | |
47 | /// ``` | |
f2b60f7d | 48 | /// 3. If you are on 1.60.0 or later, `Vec::spare_capacity_mut()` is available: |
3c0e092e XL |
49 | /// ```rust,ignore |
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 | |
54 | /// ``` | |
a2a8927a | 55 | #[clippy::version = "1.58.0"] |
3c0e092e XL |
56 | pub UNINIT_VEC, |
57 | correctness, | |
58 | "Vec with uninitialized data" | |
59 | } | |
60 | ||
61 | declare_lint_pass!(UninitVec => [UNINIT_VEC]); | |
62 | ||
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); | |
71 | } | |
72 | } | |
73 | ||
74 | if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) { | |
75 | handle_uninit_vec_pair(cx, stmt, expr); | |
76 | } | |
77 | } | |
78 | } | |
79 | } | |
80 | ||
5099ac24 | 81 | fn handle_uninit_vec_pair<'tcx>( |
3c0e092e XL |
82 | cx: &LateContext<'tcx>, |
83 | maybe_init_or_reserve: &'tcx Stmt<'tcx>, | |
84 | maybe_set_len: &'tcx Expr<'tcx>, | |
85 | ) { | |
86 | if_chain! { | |
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(); | |
add651ee | 91 | if let ty::Adt(_, args) = vec_ty.kind(); |
3c0e092e XL |
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); | |
94 | then { | |
95 | if vec.has_capacity() { | |
96 | // with_capacity / reserve -> set_len | |
97 | ||
98 | // Check T of Vec<T> | |
add651ee | 99 | if !is_uninit_value_valid_for_ty(cx, args.type_at(0)) { |
3c0e092e | 100 | // FIXME: #7698, false positive of the internal lints |
923072b8 | 101 | #[expect(clippy::collapsible_span_lint_calls)] |
3c0e092e XL |
102 | span_lint_and_then( |
103 | cx, | |
104 | UNINIT_VEC, | |
105 | vec![call_span, maybe_init_or_reserve.span], | |
106 | "calling `set_len()` immediately after reserving a buffer creates uninitialized values", | |
107 | |diag| { | |
108 | diag.help("initialize the buffer or wrap the content in `MaybeUninit`"); | |
109 | }, | |
110 | ); | |
111 | } | |
112 | } else { | |
113 | // new / default -> set_len | |
114 | span_lint( | |
115 | cx, | |
116 | UNINIT_VEC, | |
117 | vec![call_span, maybe_init_or_reserve.span], | |
118 | "calling `set_len()` on empty `Vec` creates out-of-bound values", | |
119 | ); | |
120 | } | |
121 | } | |
122 | } | |
123 | } | |
124 | ||
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>, | |
131 | } | |
132 | ||
133 | impl TargetVec<'_> { | |
134 | pub fn has_capacity(self) -> bool { | |
135 | !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default)) | |
136 | } | |
137 | } | |
138 | ||
139 | #[derive(Clone, Copy)] | |
140 | enum VecLocation<'tcx> { | |
141 | Local(HirId), | |
142 | Expr(&'tcx Expr<'tcx>), | |
143 | } | |
144 | ||
145 | impl<'tcx> VecLocation<'tcx> { | |
146 | pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { | |
147 | match self { | |
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), | |
150 | } | |
151 | } | |
152 | } | |
153 | ||
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>> { | |
157 | match stmt.kind { | |
158 | StmtKind::Local(local) => { | |
159 | if_chain! { | |
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); | |
163 | then { | |
164 | return Some(TargetVec { | |
165 | location: VecLocation::Local(hir_id), | |
166 | init_kind: Some(init_kind), | |
167 | }) | |
168 | } | |
169 | } | |
170 | }, | |
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), | |
177 | }); | |
178 | } | |
179 | }, | |
f2b60f7d | 180 | ExprKind::MethodCall(path, self_expr, [_], _) if is_reserve(cx, path, self_expr) => { |
3c0e092e XL |
181 | return Some(TargetVec { |
182 | location: VecLocation::Expr(self_expr), | |
183 | init_kind: None, | |
184 | }); | |
185 | }, | |
186 | _ => (), | |
187 | }, | |
188 | StmtKind::Item(_) => (), | |
189 | } | |
190 | None | |
191 | } | |
192 | ||
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" | |
196 | } | |
197 | ||
198 | /// Returns self if the expression is `Vec::set_len()` | |
5099ac24 | 199 | fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> { |
3c0e092e XL |
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 | |
ed00b5ec | 204 | match (block.stmts.first().map(|stmt| &stmt.kind), block.expr) { |
3c0e092e XL |
205 | (None, Some(expr)) => Some(expr), |
206 | (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr), | |
207 | _ => None, | |
208 | } | |
209 | } else { | |
210 | None | |
211 | } | |
212 | }); | |
213 | match expr.kind { | |
2b03887a | 214 | ExprKind::MethodCall(path, self_expr, [arg], _) => { |
3c0e092e | 215 | let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs(); |
2b03887a FG |
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) | |
219 | { | |
3c0e092e XL |
220 | Some((self_expr, expr.span)) |
221 | } else { | |
222 | None | |
223 | } | |
224 | }, | |
225 | _ => None, | |
226 | } | |
227 | } |