| use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then}; | |
| use clippy_utils::higher::{VecInitKind, get_vec_init_kind}; | |
| use clippy_utils::source::{indent_of, snippet}; | |
| use clippy_utils::{get_enclosing_block, sym}; | |
| use rustc_errors::Applicability; | |
| use rustc_hir::def::Res; | |
| use rustc_hir::intravisit::{Visitor, walk_expr}; | |
| use rustc_hir::{self as hir, Expr, ExprKind, HirId, LetStmt, PatKind, PathSegment, QPath, StmtKind}; | |
| use rustc_lint::{LateContext, LateLintPass, declare_lint_pass}; | |
| declare_clippy_lint! { | |
| /// ### What it does | |
| /// This lint catches reads into a zero-length `Vec`. | |
| /// Especially in the case of a call to `with_capacity`, this lint warns that read | |
| /// gets the number of bytes from the `Vec`'s length, not its capacity. | |
| /// | |
| /// ### Why is this bad? | |
| /// Reading zero bytes is almost certainly not the intended behavior. | |
| /// | |
| /// ### Known problems | |
| /// In theory, a very unusual read implementation could assign some semantic meaning | |
| /// to zero-byte reads. But it seems exceptionally unlikely that code intending to do | |
| /// a zero-byte read would allocate a `Vec` for it. | |
| /// | |
| /// ### Example | |
| /// ```no_run | |
| /// use std::io; | |
| /// fn foo<F: io::Read>(mut f: F) { | |
| /// let mut data = Vec::with_capacity(100); | |
| /// f.read(&mut data).unwrap(); | |
| /// } | |
| /// ``` | |
| /// Use instead: | |
| /// ```no_run | |
| /// use std::io; | |
| /// fn foo<F: io::Read>(mut f: F) { | |
| /// let mut data = Vec::with_capacity(100); | |
| /// data.resize(100, 0); | |
| /// f.read(&mut data).unwrap(); | |
| /// } | |
| /// ``` | |
| #[clippy::version = "1.63.0"] | |
| pub READ_ZERO_BYTE_VEC, | |
| nursery, | |
| "checks for reads into a zero-length `Vec`" | |
| } | |
| declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]); | |
| impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { | |
| fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) { | |
| for stmt in block.stmts { | |
| if stmt.span.from_expansion() { | |
| return; | |
| } | |
| if let StmtKind::Let(local) = stmt.kind | |
| && let LetStmt { | |
| pat, init: Some(init), .. | |
| } = local | |
| && let PatKind::Binding(_, id, ident, _) = pat.kind | |
| && let Some(vec_init_kind) = get_vec_init_kind(cx, init) | |
| { | |
| let mut visitor = ReadVecVisitor { | |
| local_id: id, | |
| read_zero_expr: None, | |
| has_resize: false, | |
| }; | |
| let Some(enclosing_block) = get_enclosing_block(cx, id) else { | |
| return; | |
| }; | |
| visitor.visit_block(enclosing_block); | |
| if let Some(expr) = visitor.read_zero_expr { | |
| let applicability = Applicability::MaybeIncorrect; | |
| match vec_init_kind { | |
| VecInitKind::WithConstCapacity(len) => span_lint_hir_and_then( | |
| cx, | |
| READ_ZERO_BYTE_VEC, | |
| expr.hir_id, | |
| expr.span, | |
| "reading zero byte data to `Vec`", | |
| |diag| { | |
| let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); | |
| let indent = indent_of(cx, span).unwrap_or(0); | |
| diag.span_suggestion( | |
| span.shrink_to_lo(), | |
| "try", | |
| format!("{ident}.resize({len}, 0);\n{}", " ".repeat(indent)), | |
| applicability, | |
| ); | |
| }, | |
| ), | |
| VecInitKind::WithExprCapacity(hir_id) => { | |
| let e = cx.tcx.hir_expect_expr(hir_id); | |
| span_lint_hir_and_then( | |
| cx, | |
| READ_ZERO_BYTE_VEC, | |
| expr.hir_id, | |
| expr.span, | |
| "reading zero byte data to `Vec`", | |
| |diag| { | |
| let span = first_stmt_containing_expr(cx, expr).map_or(expr.span, |stmt| stmt.span); | |
| let indent = indent_of(cx, span).unwrap_or(0); | |
| diag.span_suggestion( | |
| span.shrink_to_lo(), | |
| "try", | |
| format!( | |
| "{ident}.resize({}, 0);\n{}", | |
| snippet(cx, e.span, ".."), | |
| " ".repeat(indent) | |
| ), | |
| applicability, | |
| ); | |
| }, | |
| ); | |
| }, | |
| _ => { | |
| span_lint_hir( | |
| cx, | |
| READ_ZERO_BYTE_VEC, | |
| expr.hir_id, | |
| expr.span, | |
| "reading zero byte data to `Vec`", | |
| ); | |
| }, | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| fn first_stmt_containing_expr<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx hir::Stmt<'tcx>> { | |
| cx.tcx.hir_parent_iter(expr.hir_id).find_map(|(_, node)| { | |
| if let hir::Node::Stmt(stmt) = node { | |
| Some(stmt) | |
| } else { | |
| None | |
| } | |
| }) | |
| } | |
| struct ReadVecVisitor<'tcx> { | |
| local_id: HirId, | |
| read_zero_expr: Option<&'tcx Expr<'tcx>>, | |
| has_resize: bool, | |
| } | |
| impl<'tcx> Visitor<'tcx> for ReadVecVisitor<'tcx> { | |
| fn visit_expr(&mut self, e: &'tcx Expr<'tcx>) { | |
| if let ExprKind::MethodCall(path, receiver, args, _) = e.kind { | |
| let PathSegment { ident, .. } = *path; | |
| match ident.name { | |
| sym::read | sym::read_exact => { | |
| let [arg] = args else { return }; | |
| if let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind | |
| && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind | |
| && let [inner_seg] = inner_path.segments | |
| && let Res::Local(res_id) = inner_seg.res | |
| && self.local_id == res_id | |
| { | |
| self.read_zero_expr = Some(e); | |
| return; | |
| } | |
| }, | |
| sym::resize => { | |
| // If the Vec is resized, then it's a valid read | |
| if let ExprKind::Path(QPath::Resolved(_, inner_path)) = receiver.kind | |
| && let Res::Local(res_id) = inner_path.res | |
| && self.local_id == res_id | |
| { | |
| self.has_resize = true; | |
| return; | |
| } | |
| }, | |
| _ => {}, | |
| } | |
| } | |
| if !self.has_resize && self.read_zero_expr.is_none() { | |
| walk_expr(self, e); | |
| } | |
| } | |
| } |