Skip to content

Commit fecee63

Browse files
New lint: unbuffered_bytes
1 parent 85bbba6 commit fecee63

8 files changed

+127
-6
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -6138,6 +6138,7 @@ Released 2018-09-13
61386138
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
61396139
[`type_id_on_box`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_id_on_box
61406140
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
6141+
[`unbuffered_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#unbuffered_bytes
61416142
[`unchecked_duration_subtraction`]: https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_duration_subtraction
61426143
[`unconditional_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#unconditional_recursion
61436144
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
479479
crate::methods::SUSPICIOUS_SPLITN_INFO,
480480
crate::methods::SUSPICIOUS_TO_OWNED_INFO,
481481
crate::methods::TYPE_ID_ON_BOX_INFO,
482+
crate::methods::UNBUFFERED_BYTES_INFO,
482483
crate::methods::UNINIT_ASSUMED_INIT_INFO,
483484
crate::methods::UNIT_HASH_INFO,
484485
crate::methods::UNNECESSARY_FALLIBLE_CONVERSIONS_INFO,

clippy_lints/src/methods/mod.rs

+30
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ mod suspicious_map;
113113
mod suspicious_splitn;
114114
mod suspicious_to_owned;
115115
mod type_id_on_box;
116+
mod unbuffered_bytes;
116117
mod uninit_assumed_init;
117118
mod unit_hash;
118119
mod unnecessary_fallible_conversions;
@@ -4392,6 +4393,33 @@ declare_clippy_lint! {
43924393
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
43934394
}
43944395

4396+
declare_clippy_lint! {
4397+
/// ### What it does
4398+
/// Checks for `Read::bytes()` on an unbuffered Read type
4399+
///
4400+
/// ### Why is this bad?
4401+
/// The default implementation calls `read` for each byte, which can be very inefficient for data that’s not in memory, such as `File`.
4402+
///
4403+
/// ### Example
4404+
/// ```no_run
4405+
/// use std::io::Read;
4406+
/// use std::fs::File;
4407+
/// let file = File::open("./bytes.txt").unwrap();
4408+
/// file.bytes();
4409+
/// ```
4410+
/// Use instead:
4411+
/// ```no_run
4412+
/// use std::io::{BufReader, Read};
4413+
/// use std::fs::File;
4414+
/// let file = BufReader::new(std::fs::File::open("./bytes.txt").unwrap());
4415+
/// file.bytes();
4416+
/// ```
4417+
#[clippy::version = "1.86.0"]
4418+
pub UNBUFFERED_BYTES,
4419+
perf,
4420+
"calling .bytes() is very inefficient when data is not in memory"
4421+
}
4422+
43954423
pub struct Methods {
43964424
avoid_breaking_exported_api: bool,
43974425
msrv: Msrv,
@@ -4561,6 +4589,7 @@ impl_lint_pass!(Methods => [
45614589
USELESS_NONZERO_NEW_UNCHECKED,
45624590
MANUAL_REPEAT_N,
45634591
SLICED_STRING_AS_BYTES,
4592+
UNBUFFERED_BYTES,
45644593
]);
45654594

45664595
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4834,6 +4863,7 @@ impl Methods {
48344863
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
48354864
("as_ref", []) => useless_asref::check(cx, expr, "as_ref", recv),
48364865
("assume_init", []) => uninit_assumed_init::check(cx, expr, recv),
4866+
("bytes", []) => unbuffered_bytes::check(cx, expr, recv),
48374867
("cloned", []) => {
48384868
cloned_instead_of_copied::check(cx, expr, recv, span, &self.msrv);
48394869
option_as_ref_cloned::check(cx, recv, span);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use super::UNBUFFERED_BYTES;
2+
use clippy_utils::diagnostics::span_lint_and_help;
3+
use clippy_utils::ty::implements_trait;
4+
use clippy_utils::{get_trait_def_id, is_trait_method};
5+
use rustc_hir as hir;
6+
use rustc_lint::LateContext;
7+
use rustc_span::sym;
8+
9+
/// lint to detect `.bytes()` on an unbuffered type such as a `File` or `TcpStream`
10+
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) {
11+
// Retrieve the DefId of the BufRead trait
12+
// Sadly BufRead does not have a diagnostic item or a lang item
13+
let Some(buf_read) = get_trait_def_id(cx.tcx, &["std", "io", "BufRead"]) else {
14+
return;
15+
};
16+
17+
let typ = cx.typeck_results().expr_ty_adjusted(recv);
18+
if
19+
// The .bytes() call is a call from the given trait
20+
is_trait_method(cx, expr, sym::IoRead)
21+
// And the implementor of the trait is not buffered
22+
&& !implements_trait(cx, typ, buf_read, &[])
23+
{
24+
span_lint_and_help(
25+
cx,
26+
UNBUFFERED_BYTES,
27+
expr.span,
28+
"calling .bytes() is very inefficient when data is not in memory",
29+
None,
30+
"consider using `BufReader`",
31+
);
32+
}
33+
}

tests/ui/bytes_count_to_len.fixed

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::bytes_count_to_len)]
22
use std::fs::File;
3-
use std::io::Read;
3+
use std::io::{BufReader, Read};
44

55
fn main() {
66
// should fix, because type is String
@@ -26,8 +26,8 @@ fn main() {
2626
bytes.bytes().count();
2727

2828
// The type is File, so should not fix
29-
let _ = File::open("foobar").unwrap().bytes().count();
29+
let _ = BufReader::new(File::open("foobar").unwrap()).bytes().count();
3030

31-
let f = File::open("foobar").unwrap();
31+
let f = BufReader::new(File::open("foobar").unwrap());
3232
let _ = f.bytes().count();
3333
}

tests/ui/bytes_count_to_len.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::bytes_count_to_len)]
22
use std::fs::File;
3-
use std::io::Read;
3+
use std::io::{BufReader, Read};
44

55
fn main() {
66
// should fix, because type is String
@@ -26,8 +26,8 @@ fn main() {
2626
bytes.bytes().count();
2727

2828
// The type is File, so should not fix
29-
let _ = File::open("foobar").unwrap().bytes().count();
29+
let _ = BufReader::new(File::open("foobar").unwrap()).bytes().count();
3030

31-
let f = File::open("foobar").unwrap();
31+
let f = BufReader::new(File::open("foobar").unwrap());
3232
let _ = f.bytes().count();
3333
}

tests/ui/unbuffered_bytes.rs

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![warn(clippy::unbuffered_bytes)]
2+
3+
use std::fs::File;
4+
use std::io::{BufReader, Cursor, Read};
5+
use std::net::TcpStream;
6+
7+
fn main() {
8+
// File is not buffered, should complain
9+
let file = File::open("./bytes.txt").unwrap();
10+
file.bytes();
11+
12+
// TcpStream is not buffered, should complain
13+
let tcp_stream: TcpStream = todo!();
14+
tcp_stream.bytes();
15+
16+
// BufReader<File> is buffered, should not complain
17+
let file = BufReader::new(File::open("./bytes.txt").unwrap());
18+
file.bytes();
19+
20+
// Cursor is buffered, should not complain
21+
let cursor = Cursor::new(Vec::new());
22+
cursor.bytes();
23+
}
24+
25+
fn use_read<R: Read>(r: R) {
26+
// Callers of `use_read` may choose a `R` that is not buffered
27+
r.bytes();
28+
}

tests/ui/unbuffered_bytes.stderr

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
error: calling .bytes() is very inefficient when data is not in memory
2+
--> tests/ui/unbuffered_bytes.rs:10:5
3+
|
4+
LL | file.bytes();
5+
| ^^^^^^^^^^^^
6+
|
7+
= help: consider using `BufReader`
8+
= note: `-D clippy::unbuffered-bytes` implied by `-D warnings`
9+
= help: to override `-D warnings` add `#[allow(clippy::unbuffered_bytes)]`
10+
11+
error: calling .bytes() is very inefficient when data is not in memory
12+
--> tests/ui/unbuffered_bytes.rs:14:5
13+
|
14+
LL | tcp_stream.bytes();
15+
| ^^^^^^^^^^^^^^^^^^
16+
|
17+
= help: consider using `BufReader`
18+
19+
error: calling .bytes() is very inefficient when data is not in memory
20+
--> tests/ui/unbuffered_bytes.rs:27:5
21+
|
22+
LL | r.bytes();
23+
| ^^^^^^^^^
24+
|
25+
= help: consider using `BufReader`
26+
27+
error: aborting due to 3 previous errors
28+

0 commit comments

Comments
 (0)