|
| 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_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; |
| 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 | + /// ``` |
| 48 | + /// 3. If you are on nightly, `Vec::spare_capacity_mut()` is available: |
| 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 | + /// ``` |
| 55 | + pub UNINIT_VEC, |
| 56 | + correctness, |
| 57 | + "Vec with uninitialized data" |
| 58 | +} |
| 59 | + |
| 60 | +declare_lint_pass!(UninitVec => [UNINIT_VEC]); |
| 61 | + |
| 62 | +// FIXME: update to a visitor-based implementation. |
| 63 | +// Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368 |
| 64 | +impl<'tcx> LateLintPass<'tcx> for UninitVec { |
| 65 | + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { |
| 66 | + if !in_external_macro(cx.tcx.sess, block.span) { |
| 67 | + for w in block.stmts.windows(2) { |
| 68 | + if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind { |
| 69 | + handle_uninit_vec_pair(cx, &w[0], expr); |
| 70 | + } |
| 71 | + } |
| 72 | + |
| 73 | + if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) { |
| 74 | + handle_uninit_vec_pair(cx, stmt, expr); |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +fn handle_uninit_vec_pair( |
| 81 | + cx: &LateContext<'tcx>, |
| 82 | + maybe_init_or_reserve: &'tcx Stmt<'tcx>, |
| 83 | + maybe_set_len: &'tcx Expr<'tcx>, |
| 84 | +) { |
| 85 | + if_chain! { |
| 86 | + if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve); |
| 87 | + if let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len); |
| 88 | + if vec.location.eq_expr(cx, set_len_self); |
| 89 | + if let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind(); |
| 90 | + if let ty::Adt(_, substs) = vec_ty.kind(); |
| 91 | + // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()` |
| 92 | + if !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id); |
| 93 | + then { |
| 94 | + if vec.has_capacity() { |
| 95 | + // with_capacity / reserve -> set_len |
| 96 | + |
| 97 | + // Check T of Vec<T> |
| 98 | + if !is_uninit_value_valid_for_ty(cx, substs.type_at(0)) { |
| 99 | + // FIXME: #7698, false positive of the internal lints |
| 100 | + #[allow(clippy::collapsible_span_lint_calls)] |
| 101 | + span_lint_and_then( |
| 102 | + cx, |
| 103 | + UNINIT_VEC, |
| 104 | + vec![call_span, maybe_init_or_reserve.span], |
| 105 | + "calling `set_len()` immediately after reserving a buffer creates uninitialized values", |
| 106 | + |diag| { |
| 107 | + diag.help("initialize the buffer or wrap the content in `MaybeUninit`"); |
| 108 | + }, |
| 109 | + ); |
| 110 | + } |
| 111 | + } else { |
| 112 | + // new / default -> set_len |
| 113 | + span_lint( |
| 114 | + cx, |
| 115 | + UNINIT_VEC, |
| 116 | + vec![call_span, maybe_init_or_reserve.span], |
| 117 | + "calling `set_len()` on empty `Vec` creates out-of-bound values", |
| 118 | + ); |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +/// The target `Vec` that is initialized or reserved |
| 125 | +#[derive(Clone, Copy)] |
| 126 | +struct TargetVec<'tcx> { |
| 127 | + location: VecLocation<'tcx>, |
| 128 | + /// `None` if `reserve()` |
| 129 | + init_kind: Option<VecInitKind>, |
| 130 | +} |
| 131 | + |
| 132 | +impl TargetVec<'_> { |
| 133 | + pub fn has_capacity(self) -> bool { |
| 134 | + !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default)) |
| 135 | + } |
| 136 | +} |
| 137 | + |
| 138 | +#[derive(Clone, Copy)] |
| 139 | +enum VecLocation<'tcx> { |
| 140 | + Local(HirId), |
| 141 | + Expr(&'tcx Expr<'tcx>), |
| 142 | +} |
| 143 | + |
| 144 | +impl<'tcx> VecLocation<'tcx> { |
| 145 | + pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { |
| 146 | + match self { |
| 147 | + VecLocation::Local(hir_id) => path_to_local_id(expr, hir_id), |
| 148 | + VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr), |
| 149 | + } |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +/// Finds the target location where the result of `Vec` initialization is stored |
| 154 | +/// or `self` expression for `Vec::reserve()`. |
| 155 | +fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> { |
| 156 | + match stmt.kind { |
| 157 | + StmtKind::Local(local) => { |
| 158 | + if_chain! { |
| 159 | + if let Some(init_expr) = local.init; |
| 160 | + if let PatKind::Binding(_, hir_id, _, None) = local.pat.kind; |
| 161 | + if let Some(init_kind) = get_vec_init_kind(cx, init_expr); |
| 162 | + then { |
| 163 | + return Some(TargetVec { |
| 164 | + location: VecLocation::Local(hir_id), |
| 165 | + init_kind: Some(init_kind), |
| 166 | + }) |
| 167 | + } |
| 168 | + } |
| 169 | + }, |
| 170 | + StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind { |
| 171 | + ExprKind::Assign(lhs, rhs, _span) => { |
| 172 | + if let Some(init_kind) = get_vec_init_kind(cx, rhs) { |
| 173 | + return Some(TargetVec { |
| 174 | + location: VecLocation::Expr(lhs), |
| 175 | + init_kind: Some(init_kind), |
| 176 | + }); |
| 177 | + } |
| 178 | + }, |
| 179 | + ExprKind::MethodCall(path, _, [self_expr, _], _) if is_reserve(cx, path, self_expr) => { |
| 180 | + return Some(TargetVec { |
| 181 | + location: VecLocation::Expr(self_expr), |
| 182 | + init_kind: None, |
| 183 | + }); |
| 184 | + }, |
| 185 | + _ => (), |
| 186 | + }, |
| 187 | + StmtKind::Item(_) => (), |
| 188 | + } |
| 189 | + None |
| 190 | +} |
| 191 | + |
| 192 | +fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool { |
| 193 | + is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr).peel_refs(), sym::Vec) |
| 194 | + && path.ident.name.as_str() == "reserve" |
| 195 | +} |
| 196 | + |
| 197 | +/// Returns self if the expression is `Vec::set_len()` |
| 198 | +fn extract_set_len_self(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> { |
| 199 | + // peel unsafe blocks in `unsafe { vec.set_len() }` |
| 200 | + let expr = peel_hir_expr_while(expr, |e| { |
| 201 | + if let ExprKind::Block(block, _) = e.kind { |
| 202 | + // Extract the first statement/expression |
| 203 | + match (block.stmts.get(0).map(|stmt| &stmt.kind), block.expr) { |
| 204 | + (None, Some(expr)) => Some(expr), |
| 205 | + (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr), |
| 206 | + _ => None, |
| 207 | + } |
| 208 | + } else { |
| 209 | + None |
| 210 | + } |
| 211 | + }); |
| 212 | + match expr.kind { |
| 213 | + ExprKind::MethodCall(path, _, [self_expr, _], _) => { |
| 214 | + let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs(); |
| 215 | + if is_type_diagnostic_item(cx, self_type, sym::Vec) && path.ident.name.as_str() == "set_len" { |
| 216 | + Some((self_expr, expr.span)) |
| 217 | + } else { |
| 218 | + None |
| 219 | + } |
| 220 | + }, |
| 221 | + _ => None, |
| 222 | + } |
| 223 | +} |
0 commit comments