Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Warn about read into zero-length Vec #8964

Merged
merged 1 commit into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3678,6 +3678,7 @@ Released 2018-09-13
[`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer
[`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init
[`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex
[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec
[`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl
[`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation
[`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![
LintId::of(ranges::RANGE_ZIP_WITH_LEN),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT),
LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
LintId::of(redundant_clone::REDUNDANT_CLONE),
LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL),
LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_correctness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve
LintId::of(ptr::INVALID_NULL_PTR_USAGE),
LintId::of(ptr::MUT_FROM_REF),
LintId::of(ranges::REVERSED_EMPTY_RANGES),
LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC),
LintId::of(regex::INVALID_REGEX),
LintId::of(self_assignment::SELF_ASSIGNMENT),
LintId::of(serde_api::SERDE_API_MISUSE),
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/lib.register_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ store.register_lints(&[
ranges::RANGE_ZIP_WITH_LEN,
ranges::REVERSED_EMPTY_RANGES,
rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT,
read_zero_byte_vec::READ_ZERO_BYTE_VEC,
redundant_clone::REDUNDANT_CLONE,
redundant_closure_call::REDUNDANT_CLOSURE_CALL,
redundant_else::REDUNDANT_ELSE,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ mod pub_use;
mod question_mark;
mod ranges;
mod rc_clone_in_vec_init;
mod read_zero_byte_vec;
mod redundant_clone;
mod redundant_closure_call;
mod redundant_else;
Expand Down Expand Up @@ -909,6 +910,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef));
store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch));
store.register_late_pass(|| Box::new(as_underscore::AsUnderscore));
store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec));
// add lints here, do not remove this comment, it's used in `new_lint`
}

Expand Down
142 changes: 142 additions & 0 deletions clippy_lints/src/read_zero_byte_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use clippy_utils::{
diagnostics::{span_lint, span_lint_and_sugg},
higher::{get_vec_init_kind, VecInitKind},
source::snippet,
visitors::expr_visitor_no_bodies,
};
use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind};
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

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
/// ```rust
/// 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:
/// ```rust
/// 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,
correctness,
"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 (idx, stmt) in block.stmts.iter().enumerate() {
if !stmt.span.from_expansion()
// matches `let v = Vec::new();`
&& let StmtKind::Local(local) = stmt.kind
&& let Local { pat, init: Some(init), .. } = local
&& let PatKind::Binding(_, _, ident, _) = pat.kind
&& let Some(vec_init_kind) = get_vec_init_kind(cx, init)
{
// finds use of `_.read(&mut v)`
let mut read_found = false;
let mut visitor = expr_visitor_no_bodies(|expr| {
if let ExprKind::MethodCall(path, [_self, arg], _) = expr.kind
&& let PathSegment { ident: read_or_read_exact, .. } = *path
&& matches!(read_or_read_exact.as_str(), "read" | "read_exact")
&& 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
&& ident.name == inner_seg.ident.name
{
read_found = true;
}
!read_found
});

let next_stmt_span;
if idx == block.stmts.len() - 1 {
// case { .. stmt; expr }
if let Some(e) = block.expr {
visitor.visit_expr(e);
next_stmt_span = e.span;
} else {
return;
}
} else {
// case { .. stmt; stmt; .. }
let next_stmt = &block.stmts[idx + 1];
visitor.visit_stmt(next_stmt);
next_stmt_span = next_stmt.span;
}
drop(visitor);

if read_found && !next_stmt_span.from_expansion() {
let applicability = Applicability::MaybeIncorrect;
match vec_init_kind {
VecInitKind::WithConstCapacity(len) => {
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
"reading zero byte data to `Vec`",
"try",
format!("{}.resize({}, 0); {}",
ident.as_str(),
len,
snippet(cx, next_stmt_span, "..")
),
applicability,
);
}
VecInitKind::WithExprCapacity(hir_id) => {
let e = cx.tcx.hir().expect_expr(hir_id);
span_lint_and_sugg(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
"reading zero byte data to `Vec`",
"try",
format!("{}.resize({}, 0); {}",
ident.as_str(),
snippet(cx, e.span, ".."),
snippet(cx, next_stmt_span, "..")
),
applicability,
);
}
_ => {
span_lint(
cx,
READ_ZERO_BYTE_VEC,
next_stmt_span,
"reading zero byte data to `Vec`",
);

}
}
}
}
}
}
}
87 changes: 87 additions & 0 deletions tests/ui/read_zero_byte_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#![warn(clippy::read_zero_byte_vec)]
#![allow(clippy::unused_io_amount)]
use std::fs::File;
use std::io;
use std::io::prelude::*;

extern crate futures;
use futures::io::{AsyncRead, AsyncReadExt};
use tokio::io::{AsyncRead as TokioAsyncRead, AsyncReadExt as _, AsyncWrite as TokioAsyncWrite, AsyncWriteExt as _};

fn test() -> io::Result<()> {
let cap = 1000;
let mut f = File::open("foo.txt").unwrap();

// should lint
let mut data = Vec::with_capacity(20);
f.read_exact(&mut data).unwrap();

// should lint
let mut data2 = Vec::with_capacity(cap);
f.read_exact(&mut data2)?;

// should lint
let mut data3 = Vec::new();
f.read_exact(&mut data3)?;

// should lint
let mut data4 = vec![];
let _ = f.read(&mut data4)?;

// should lint
let _ = {
let mut data5 = Vec::new();
f.read(&mut data5)
};

// should lint
let _ = {
let mut data6: Vec<u8> = Default::default();
f.read(&mut data6)
};

// should not lint
let mut buf = [0u8; 100];
f.read(&mut buf)?;

// should not lint
let mut empty = vec![];
let mut data7 = vec![];
f.read(&mut empty);

// should not lint
f.read(&mut data7);

// should not lint
let mut data8 = Vec::new();
data8.resize(100, 0);
f.read_exact(&mut data8)?;

// should not lint
let mut data9 = vec![1, 2, 3];
f.read_exact(&mut data9)?;

Ok(())
}

async fn test_futures<R: AsyncRead + Unpin>(r: &mut R) {
// should lint
let mut data = Vec::new();
r.read(&mut data).await.unwrap();

// should lint
let mut data2 = Vec::new();
r.read_exact(&mut data2).await.unwrap();
}

async fn test_tokio<R: TokioAsyncRead + Unpin>(r: &mut R) {
// should lint
let mut data = Vec::new();
r.read(&mut data).await.unwrap();

// should lint
let mut data2 = Vec::new();
r.read_exact(&mut data2).await.unwrap();
}

fn main() {}
64 changes: 64 additions & 0 deletions tests/ui/read_zero_byte_vec.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:17:5
|
LL | f.read_exact(&mut data).unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();`
|
= note: `-D clippy::read-zero-byte-vec` implied by `-D warnings`

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:21:5
|
LL | f.read_exact(&mut data2)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;`

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:25:5
|
LL | f.read_exact(&mut data3)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:29:5
|
LL | let _ = f.read(&mut data4)?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:34:9
|
LL | f.read(&mut data5)
| ^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:40:9
|
LL | f.read(&mut data6)
| ^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:70:5
|
LL | r.read(&mut data).await.unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:74:5
|
LL | r.read_exact(&mut data2).await.unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:80:5
|
LL | r.read(&mut data).await.unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: reading zero byte data to `Vec`
--> $DIR/read_zero_byte_vec.rs:84:5
|
LL | r.read_exact(&mut data2).await.unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 10 previous errors