177 lines
6.7 KiB
Rust
177 lines
6.7 KiB
Rust
use clippy_utils::diagnostics::{span_lint_hir, span_lint_hir_and_then};
|
|
use clippy_utils::get_enclosing_block;
|
|
use clippy_utils::higher::{get_vec_init_kind, VecInitKind};
|
|
use clippy_utils::source::snippet;
|
|
|
|
use hir::{Expr, ExprKind, HirId, Local, PatKind, PathSegment, QPath, StmtKind};
|
|
use rustc_errors::Applicability;
|
|
use rustc_hir as hir;
|
|
use rustc_hir::def::Res;
|
|
use rustc_hir::intravisit::{walk_expr, Visitor};
|
|
use rustc_lint::{LateContext, LateLintPass};
|
|
use rustc_session::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::Local(local) = stmt.kind
|
|
&& let Local {
|
|
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| {
|
|
diag.span_suggestion(
|
|
expr.span,
|
|
"try",
|
|
format!("{}.resize({len}, 0); {}", ident.as_str(), snippet(cx, expr.span, "..")),
|
|
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| {
|
|
diag.span_suggestion(
|
|
expr.span,
|
|
"try",
|
|
format!(
|
|
"{}.resize({}, 0); {}",
|
|
ident.as_str(),
|
|
snippet(cx, e.span, ".."),
|
|
snippet(cx, expr.span, "..")
|
|
),
|
|
applicability,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
_ => {
|
|
span_lint_hir(
|
|
cx,
|
|
READ_ZERO_BYTE_VEC,
|
|
expr.hir_id,
|
|
expr.span,
|
|
"reading zero byte data to `Vec`",
|
|
);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.as_str() {
|
|
"read" | "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;
|
|
}
|
|
},
|
|
"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);
|
|
}
|
|
}
|
|
}
|