2 diagnostics::{span_lint, span_lint_and_sugg},
3 higher::{get_vec_init_kind, VecInitKind},
5 visitors::for_each_expr,
7 use core::ops::ControlFlow;
8 use hir::{Expr, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
9 use rustc_errors::Applicability;
11 use rustc_lint::{LateContext, LateLintPass};
12 use rustc_session::{declare_lint_pass, declare_tool_lint};
14 declare_clippy_lint! {
16 /// This lint catches reads into a zero-length `Vec`.
17 /// Especially in the case of a call to `with_capacity`, this lint warns that read
18 /// gets the number of bytes from the `Vec`'s length, not its capacity.
20 /// ### Why is this bad?
21 /// Reading zero bytes is almost certainly not the intended behavior.
23 /// ### Known problems
24 /// In theory, a very unusual read implementation could assign some semantic meaning
25 /// to zero-byte reads. But it seems exceptionally unlikely that code intending to do
26 /// a zero-byte read would allocate a `Vec` for it.
31 /// fn foo<F: io::Read>(mut f: F) {
32 /// let mut data = Vec::with_capacity(100);
33 /// f.read(&mut data).unwrap();
39 /// fn foo<F: io::Read>(mut f: F) {
40 /// let mut data = Vec::with_capacity(100);
41 /// data.resize(100, 0);
42 /// f.read(&mut data).unwrap();
45 #[clippy::version = "1.63.0"]
46 pub READ_ZERO_BYTE_VEC,
48 "checks for reads into a zero-length `Vec`"
50 declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]);
52 impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec {
53 fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) {
54 for (idx, stmt) in block.stmts.iter().enumerate() {
55 if !stmt.span.from_expansion()
56 // matches `let v = Vec::new();`
57 && let StmtKind::Local(local) = stmt.kind
58 && let Local { pat, init: Some(init), .. } = local
59 && let PatKind::Binding(_, _, ident, _) = pat.kind
60 && let Some(vec_init_kind) = get_vec_init_kind(cx, init)
62 let visitor = |expr: &Expr<'_>| {
63 if let ExprKind::MethodCall(path, _, [arg], _) = expr.kind
64 && let PathSegment { ident: read_or_read_exact, .. } = *path
65 && matches!(read_or_read_exact.as_str(), "read" | "read_exact")
66 && let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind
67 && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind
68 && let [inner_seg] = inner_path.segments
69 && ident.name == inner_seg.ident.name
71 ControlFlow::Break(())
73 ControlFlow::Continue(())
77 let (read_found, next_stmt_span) =
78 if let Some(next_stmt) = block.stmts.get(idx + 1) {
79 // case { .. stmt; stmt; .. }
80 (for_each_expr(next_stmt, visitor).is_some(), next_stmt.span)
81 } else if let Some(e) = block.expr {
82 // case { .. stmt; expr }
83 (for_each_expr(e, visitor).is_some(), e.span)
88 if read_found && !next_stmt_span.from_expansion() {
89 let applicability = Applicability::MaybeIncorrect;
91 VecInitKind::WithConstCapacity(len) => {
96 "reading zero byte data to `Vec`",
98 format!("{}.resize({len}, 0); {}",
100 snippet(cx, next_stmt_span, "..")
105 VecInitKind::WithExprCapacity(hir_id) => {
106 let e = cx.tcx.hir().expect_expr(hir_id);
111 "reading zero byte data to `Vec`",
113 format!("{}.resize({}, 0); {}",
115 snippet(cx, e.span, ".."),
116 snippet(cx, next_stmt_span, "..")
126 "reading zero byte data to `Vec`",