-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Flag
bufreader.lines().filter_map(Result::ok)
as suspicious
- Loading branch information
1 parent
1d1e723
commit 1f4c5a2
Showing
9 changed files
with
155 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
use clippy_utils::{diagnostics::span_lint_and_then, is_trait_method, match_def_path, match_trait_method, paths}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Expr, ExprKind}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::sym; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Detect uses of `b.lines().filter_map(Result::ok)` or `b.lines().flat_map(Result::ok)` | ||
/// when `b` implements `BufRead`. | ||
/// | ||
/// ### Why is this bad? | ||
/// `b.lines()` might produce an infinite stream of `Err` and `filter_map(Result::ok)` | ||
/// will also run forever. Using `map_while(Result::ok)` would stop after the first | ||
/// `Err` is emitted. | ||
/// | ||
/// For example, on some platforms, `std::fs::File::open(path)` might return `Ok(fs)` | ||
/// when `path` is a directory, and reading from `fs` will then return an error. | ||
/// If `fs` was inadvertently put in a `BufReader`, calling `lines()` on it would | ||
/// return an infinite stream of `Err` variants. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// # use std::{fs::File, io::{self, BufRead, BufReader}}; | ||
/// # let _ = || -> io::Result<()> { | ||
/// let reader = BufReader::new(File::open("some path")?); | ||
/// for line in reader.lines().flat_map(Result::ok) { | ||
/// println!("{line}"); | ||
/// } | ||
/// # Ok(()) }; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// # use std::{fs::File, io::{self, BufRead, BufReader}}; | ||
/// # let _ = || -> io::Result<()> { | ||
/// let reader = BufReader::new(File::open("some path")?); | ||
/// for line in reader.lines().map_while(Result::ok) { | ||
/// println!("{line}"); | ||
/// } | ||
/// # Ok(()) }; | ||
/// ``` | ||
#[clippy::version = "1.70.0"] | ||
pub LINES_FILTER_MAP_OK, | ||
nursery, | ||
"`BufRead::lines()` might produce an infinite stream of errors" | ||
} | ||
declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]); | ||
|
||
impl LateLintPass<'_> for LinesFilterMapOk { | ||
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { | ||
if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind && | ||
is_trait_method(cx, expr, sym::Iterator) && | ||
(fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") && | ||
let ExprKind::MethodCall(lines_method, _, &[], _) = fm_receiver.kind && | ||
match_trait_method(cx, fm_receiver, &paths::STD_IO_BUFREAD) && | ||
lines_method.ident.as_str() == "lines" && | ||
let ExprKind::Path(qpath) = &fm_arg.kind && | ||
let Some(did) = cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id() && | ||
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD) | ||
{ | ||
span_lint_and_then(cx, | ||
LINES_FILTER_MAP_OK, | ||
fm_span, | ||
&format!("`{}()` will run forever on an infinite stream of `Err` returned by `lines()`", fm_method.ident), | ||
|diag| { | ||
diag.span_note( | ||
fm_receiver.span, | ||
"`BufRead::lines()` may produce an infinite stream of `Err` in case of a read error"); | ||
diag.span_suggestion(fm_span, "replace with", "map_while(Result::ok)", Applicability::MaybeIncorrect); | ||
}); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// run-rustfix | ||
|
||
#![allow(unused)] | ||
#![warn(clippy::lines_filter_map_ok)] | ||
|
||
use std::io::{self, BufRead, BufReader}; | ||
|
||
fn main() -> io::Result<()> { | ||
let f = std::fs::File::open("/tmp/t.rs")?; | ||
// Lint | ||
BufReader::new(f) | ||
.lines() | ||
.map_while(Result::ok) | ||
.for_each(|l| println!("{l}")); | ||
// Lint | ||
let f = std::fs::File::open("/tmp/t.rs")?; | ||
BufReader::new(f) | ||
.lines() | ||
.map_while(Result::ok) | ||
.for_each(|l| println!("{l}")); | ||
let s = "foo\nbar\nbaz\n"; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// run-rustfix | ||
|
||
#![allow(unused)] | ||
#![warn(clippy::lines_filter_map_ok)] | ||
|
||
use std::io::{self, BufRead, BufReader}; | ||
|
||
fn main() -> io::Result<()> { | ||
let f = std::fs::File::open("/tmp/t.rs")?; | ||
// Lint | ||
BufReader::new(f) | ||
.lines() | ||
.filter_map(Result::ok) | ||
.for_each(|l| println!("{l}")); | ||
// Lint | ||
let f = std::fs::File::open("/tmp/t.rs")?; | ||
BufReader::new(f) | ||
.lines() | ||
.flat_map(Result::ok) | ||
.for_each(|l| println!("{l}")); | ||
let s = "foo\nbar\nbaz\n"; | ||
Ok(()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
error: `filter_map()` will run forever on an infinite stream of `Err` returned by `lines()` | ||
--> $DIR/lines_filter_map_ok.rs:13:10 | ||
| | ||
LL | .filter_map(Result::ok) | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: `BufRead::lines()` may produce an infinite stream of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:11:5 | ||
| | ||
LL | / BufReader::new(f) | ||
LL | | .lines() | ||
| |________________^ | ||
= note: `-D clippy::lines-filter-map-ok` implied by `-D warnings` | ||
|
||
error: `flat_map()` will run forever on an infinite stream of `Err` returned by `lines()` | ||
--> $DIR/lines_filter_map_ok.rs:19:10 | ||
| | ||
LL | .flat_map(Result::ok) | ||
| ^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: `BufRead::lines()` may produce an infinite stream of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:17:5 | ||
| | ||
LL | / BufReader::new(f) | ||
LL | | .lines() | ||
| |________________^ | ||
|
||
error: aborting due to 2 previous errors | ||
|