New lint: unbuffered_bytes

This commit is contained in:
jonathan 2025-01-27 21:50:22 +01:00 committed by Jonathan Brouwer
parent 32aef114c6
commit 8b6de49ef7
No known key found for this signature in database
GPG key ID: F13E55D38C971DEF
9 changed files with 143 additions and 6 deletions

View file

@ -482,6 +482,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::SUSPICIOUS_SPLITN_INFO,
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
crate::methods::TYPE_ID_ON_BOX_INFO,
crate::methods::UNBUFFERED_BYTES_INFO,
crate::methods::UNINIT_ASSUMED_INIT_INFO,
crate::methods::UNIT_HASH_INFO,
crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO,

View file

@ -113,6 +113,7 @@ mod suspicious_map;
mod suspicious_splitn;
mod suspicious_to_owned;
mod type_id_on_box;
mod unbuffered_bytes;
mod uninit_assumed_init;
mod unit_hash;
mod unnecessary_fallible_conversions;
@ -4406,6 +4407,33 @@ declare_clippy_lint! {
"using `Option::and_then` or `Result::and_then` to chain a computation that returns an `Option` or a `Result`"
}
declare_clippy_lint! {
/// ### What it does
/// Checks for calls to `Read::bytes` on types which don't implement `BufRead`.
///
/// ### Why is this bad?
/// The default implementation calls `read` for each byte, which can be very inefficient for data thats not in memory, such as `File`.
///
/// ### Example
/// ```no_run
/// use std::io::Read;
/// use std::fs::File;
/// let file = File::open("./bytes.txt").unwrap();
/// file.bytes();
/// ```
/// Use instead:
/// ```no_run
/// use std::io::{BufReader, Read};
/// use std::fs::File;
/// let file = BufReader::new(std::fs::File::open("./bytes.txt").unwrap());
/// file.bytes();
/// ```
#[clippy::version = "1.86.0"]
pub UNBUFFERED_BYTES,
perf,
"calling .bytes() is very inefficient when data is not in memory"
}
#[expect(clippy::struct_excessive_bools)]
pub struct Methods {
avoid_breaking_exported_api: bool,
@ -4580,6 +4608,7 @@ impl_lint_pass!(Methods => [
MANUAL_REPEAT_N,
SLICED_STRING_AS_BYTES,
RETURN_AND_THEN,
UNBUFFERED_BYTES,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@ -4856,6 +4885,7 @@ impl Methods {
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
("bytes", []) => unbuffered_bytes::check(cx, expr, recv),
("cloned", []) => {
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
option_as_ref_cloned::check(cx, recv, span);

View file

@ -0,0 +1,31 @@
use super::UNBUFFERED_BYTES;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::ty::implements_trait;
use clippy_utils::{get_trait_def_id, is_trait_method, paths};
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::sym;
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
let ty = cx.typeck_results().expr_ty_adjusted(recv);
// If the .bytes() call is a call from the Read trait
if is_trait_method(cx, expr, sym::IoRead) {
// Retrieve the DefId of the BufRead trait
// FIXME: add a diagnostic item for `BufRead`
let Some(buf_read) = get_trait_def_id(cx.tcx, &paths::BUF_READ) else {
return;
};
// And the implementor of the trait is not buffered
if !implements_trait(cx, ty, buf_read, &[]) {
span_lint_and_help(
cx,
UNBUFFERED_BYTES,
expr.span,
"calling .bytes() is very inefficient when data is not in memory",
None,
"consider using `BufReader`",
);
}
}
}