| use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; | |
| use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; | |
| use clippy_utils::res::{MaybeDef, MaybeResPath}; | |
| use clippy_utils::ty::is_uninit_value_valid_for_ty; | |
| use clippy_utils::{SpanlessEq, is_integer_literal, is_lint_allowed, peel_hir_expr_while, sym}; | |
| use rustc_hir::{Block, Expr, ExprKind, HirId, PatKind, PathSegment, Stmt, StmtKind}; | |
| use rustc_lint::{LateContext, LateLintPass}; | |
| use rustc_middle::ty; | |
| use rustc_session::declare_lint_pass; | |
| use rustc_span::Span; | |
| // TODO: add `ReadBuf` (RFC 2930) in "How to fix" once it is available in std | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// Checks for `set_len()` call that creates `Vec` with uninitialized elements. | |
| /// This is commonly caused by calling `set_len()` right after allocating or | |
| /// reserving a buffer with `new()`, `default()`, `with_capacity()`, or `reserve()`. | |
| /// | |
| /// ### Why is this bad? | |
| /// It creates a `Vec` with uninitialized data, which leads to | |
| /// undefined behavior with most safe operations. Notably, uninitialized | |
| /// `Vec<u8>` must not be used with generic `Read`. | |
| /// | |
| /// Moreover, calling `set_len()` on a `Vec` created with `new()` or `default()` | |
| /// creates out-of-bound values that lead to heap memory corruption when used. | |
| /// | |
| /// ### Known Problems | |
| /// This lint only checks directly adjacent statements. | |
| /// | |
| /// ### Example | |
| /// ```rust,ignore | |
| /// let mut vec: Vec<u8> = Vec::with_capacity(1000); | |
| /// unsafe { vec.set_len(1000); } | |
| /// reader.read(&mut vec); // undefined behavior! | |
| /// ``` | |
| /// | |
| /// ### How to fix? | |
| /// 1. Use an initialized buffer: | |
| /// ```rust,ignore | |
| /// let mut vec: Vec<u8> = vec![0; 1000]; | |
| /// reader.read(&mut vec); | |
| /// ``` | |
| /// 2. Wrap the content in `MaybeUninit`: | |
| /// ```rust,ignore | |
| /// let mut vec: Vec<MaybeUninit<T>> = Vec::with_capacity(1000); | |
| /// vec.set_len(1000); // `MaybeUninit` can be uninitialized | |
| /// ``` | |
| /// 3. If you are on 1.60.0 or later, `Vec::spare_capacity_mut()` is available: | |
| /// ```rust,ignore | |
| /// let mut vec: Vec<u8> = Vec::with_capacity(1000); | |
| /// let remaining = vec.spare_capacity_mut(); // `&mut [MaybeUninit<u8>]` | |
| /// // perform initialization with `remaining` | |
| /// vec.set_len(...); // Safe to call `set_len()` on initialized part | |
| /// ``` | |
| #[clippy::version = "1.58.0"] | |
| pub UNINIT_VEC, | |
| correctness, | |
| "Vec with uninitialized data" | |
| } | |
| declare_lint_pass!(UninitVec => [UNINIT_VEC]); | |
| // FIXME: update to a visitor-based implementation. | |
| // Threads: https://github.com/rust-lang/rust-clippy/pull/7682#discussion_r710998368 | |
| impl<'tcx> LateLintPass<'tcx> for UninitVec { | |
| fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'_>) { | |
| if !block.span.in_external_macro(cx.tcx.sess.source_map()) { | |
| for w in block.stmts.windows(2) { | |
| if let StmtKind::Expr(expr) | StmtKind::Semi(expr) = w[1].kind { | |
| handle_uninit_vec_pair(cx, &w[0], expr); | |
| } | |
| } | |
| if let (Some(stmt), Some(expr)) = (block.stmts.last(), block.expr) { | |
| handle_uninit_vec_pair(cx, stmt, expr); | |
| } | |
| } | |
| } | |
| } | |
| fn handle_uninit_vec_pair<'tcx>( | |
| cx: &LateContext<'tcx>, | |
| maybe_init_or_reserve: &'tcx Stmt<'tcx>, | |
| maybe_set_len: &'tcx Expr<'tcx>, | |
| ) { | |
| if let Some(vec) = extract_init_or_reserve_target(cx, maybe_init_or_reserve) | |
| && let Some((set_len_self, call_span)) = extract_set_len_self(cx, maybe_set_len) | |
| && vec.location.eq_expr(cx, set_len_self) | |
| && let ty::Ref(_, vec_ty, _) = cx.typeck_results().expr_ty_adjusted(set_len_self).kind() | |
| && let ty::Adt(_, args) = vec_ty.kind() | |
| // `#[allow(...)]` attribute can be set on enclosing unsafe block of `set_len()` | |
| && !is_lint_allowed(cx, UNINIT_VEC, maybe_set_len.hir_id) | |
| { | |
| if vec.has_capacity() { | |
| // with_capacity / reserve -> set_len | |
| // Check T of Vec<T> | |
| if !is_uninit_value_valid_for_ty(cx, args.type_at(0)) { | |
| span_lint_and_help( | |
| cx, | |
| UNINIT_VEC, | |
| vec![call_span, maybe_init_or_reserve.span], | |
| "calling `set_len()` immediately after reserving a buffer creates uninitialized values", | |
| None, | |
| "initialize the buffer or wrap the content in `MaybeUninit`", | |
| ); | |
| } | |
| } else { | |
| // new / default -> set_len | |
| span_lint( | |
| cx, | |
| UNINIT_VEC, | |
| vec![call_span, maybe_init_or_reserve.span], | |
| "calling `set_len()` on empty `Vec` creates out-of-bound values", | |
| ); | |
| } | |
| } | |
| } | |
| /// The target `Vec` that is initialized or reserved | |
| #[derive(Clone, Copy)] | |
| struct TargetVec<'tcx> { | |
| location: VecLocation<'tcx>, | |
| /// `None` if `reserve()` | |
| init_kind: Option<VecInitKind>, | |
| } | |
| impl TargetVec<'_> { | |
| pub fn has_capacity(self) -> bool { | |
| !matches!(self.init_kind, Some(VecInitKind::New | VecInitKind::Default)) | |
| } | |
| } | |
| #[derive(Clone, Copy)] | |
| enum VecLocation<'tcx> { | |
| Local(HirId), | |
| Expr(&'tcx Expr<'tcx>), | |
| } | |
| impl<'tcx> VecLocation<'tcx> { | |
| pub fn eq_expr(self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { | |
| match self { | |
| VecLocation::Local(hir_id) => expr.res_local_id() == Some(hir_id), | |
| VecLocation::Expr(self_expr) => SpanlessEq::new(cx).eq_expr(self_expr, expr), | |
| } | |
| } | |
| } | |
| /// Finds the target location where the result of `Vec` initialization is stored | |
| /// or `self` expression for `Vec::reserve()`. | |
| fn extract_init_or_reserve_target<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'tcx>) -> Option<TargetVec<'tcx>> { | |
| match stmt.kind { | |
| StmtKind::Let(local) => { | |
| if let Some(init_expr) = local.init | |
| && let PatKind::Binding(_, hir_id, _, None) = local.pat.kind | |
| && let Some(init_kind) = get_vec_init_kind(cx, init_expr) | |
| { | |
| return Some(TargetVec { | |
| location: VecLocation::Local(hir_id), | |
| init_kind: Some(init_kind), | |
| }); | |
| } | |
| }, | |
| StmtKind::Expr(expr) | StmtKind::Semi(expr) => match expr.kind { | |
| ExprKind::Assign(lhs, rhs, _span) => { | |
| if let Some(init_kind) = get_vec_init_kind(cx, rhs) { | |
| return Some(TargetVec { | |
| location: VecLocation::Expr(lhs), | |
| init_kind: Some(init_kind), | |
| }); | |
| } | |
| }, | |
| ExprKind::MethodCall(path, self_expr, [_], _) if is_reserve(cx, path, self_expr) => { | |
| return Some(TargetVec { | |
| location: VecLocation::Expr(self_expr), | |
| init_kind: None, | |
| }); | |
| }, | |
| _ => (), | |
| }, | |
| StmtKind::Item(_) => (), | |
| } | |
| None | |
| } | |
| fn is_reserve(cx: &LateContext<'_>, path: &PathSegment<'_>, self_expr: &Expr<'_>) -> bool { | |
| cx.typeck_results() | |
| .expr_ty(self_expr) | |
| .peel_refs() | |
| .is_diag_item(cx, sym::Vec) | |
| && path.ident.name == sym::reserve | |
| } | |
| /// Returns self if the expression is `Vec::set_len()` | |
| fn extract_set_len_self<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'_>) -> Option<(&'tcx Expr<'tcx>, Span)> { | |
| // peel unsafe blocks in `unsafe { vec.set_len() }` | |
| let expr = peel_hir_expr_while(expr, |e| { | |
| if let ExprKind::Block(block, _) = e.kind { | |
| // Extract the first statement/expression | |
| match (block.stmts.first().map(|stmt| &stmt.kind), block.expr) { | |
| (None, Some(expr)) => Some(expr), | |
| (Some(StmtKind::Expr(expr) | StmtKind::Semi(expr)), _) => Some(expr), | |
| _ => None, | |
| } | |
| } else { | |
| None | |
| } | |
| }); | |
| match expr.kind { | |
| ExprKind::MethodCall(path, self_expr, [arg], _) => { | |
| let self_type = cx.typeck_results().expr_ty(self_expr).peel_refs(); | |
| if self_type.is_diag_item(cx, sym::Vec) && path.ident.name == sym::set_len && !is_integer_literal(arg, 0) { | |
| Some((self_expr, expr.span)) | |
| } else { | |
| None | |
| } | |
| }, | |
| _ => None, | |
| } | |
| } |