forked from rust-lang/rust-clippy
-
Notifications
You must be signed in to change notification settings - Fork 1
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
799732c
commit 6601d85
Showing
8 changed files
with
215 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
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,100 @@ | ||
use clippy_utils::{ | ||
diagnostics::span_lint_and_then, is_diag_item_method, is_trait_method, match_def_path, path_to_local_id, paths, | ||
ty::match_type, | ||
}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Body, Closure, 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 `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)` | ||
/// when `lines` has type `std::io::Lines`. | ||
/// | ||
/// ### Why is this bad? | ||
/// `Lines` instances might produce a never-ending stream of `Err`, in which case | ||
/// `filter_map(Result::ok)` will enter an infinite loop while waiting for an | ||
/// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop, | ||
/// even in the absence of explicit loops in the user code. | ||
/// | ||
/// This situation can arise when working with user-provided paths. On some platforms, | ||
/// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory, | ||
/// but any later attempt to read from `fs` will return an error. | ||
/// | ||
/// ### Known problems | ||
/// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines` | ||
/// instance in all cases. There two cases where the suggestion might not be | ||
/// appropriate or necessary: | ||
/// | ||
/// - If the `Lines` instance can never produce any error, or if an error is produced | ||
/// only once just before terminating the iterator, using `map_while()` is not | ||
/// necessary but will not do any harm. | ||
/// - If the `Lines` instance can produce intermittent errors then recover and produce | ||
/// successful results, using `map_while()` would stop at the first error. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// # use std::{fs::File, io::{self, BufRead, BufReader}}; | ||
/// # let _ = || -> io::Result<()> { | ||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok); | ||
/// // If "some-path" points to a directory, the next statement never terminates: | ||
/// let first_line: Option<String> = lines.next(); | ||
/// # Ok(()) }; | ||
/// ``` | ||
/// Use instead: | ||
/// ```rust | ||
/// # use std::{fs::File, io::{self, BufRead, BufReader}}; | ||
/// # let _ = || -> io::Result<()> { | ||
/// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok); | ||
/// let first_line: Option<String> = lines.next(); | ||
/// # Ok(()) }; | ||
/// ``` | ||
#[clippy::version = "1.70.0"] | ||
pub LINES_FILTER_MAP_OK, | ||
suspicious, | ||
"filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop" | ||
} | ||
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") && | ||
match_type(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), &paths::STD_IO_LINES) | ||
{ | ||
let lint = match &fm_arg.kind { | ||
// Detect `Result::ok` | ||
ExprKind::Path(qpath) => | ||
cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did| | ||
match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(), | ||
// Detect `|x| x.ok()` | ||
ExprKind::Closure(Closure { body, .. }) => | ||
if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) && | ||
let ExprKind::MethodCall(method, receiver, [], _) = value.kind && | ||
path_to_local_id(receiver, param.pat.hir_id) && | ||
let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id) | ||
{ | ||
is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok" | ||
} else { | ||
false | ||
} | ||
_ => false, | ||
}; | ||
if lint { | ||
span_lint_and_then(cx, | ||
LINES_FILTER_MAP_OK, | ||
fm_span, | ||
&format!("`{}()` will run forever if the iterator repeatedly produces an `Err`", fm_method.ident), | ||
|diag| { | ||
diag.span_note( | ||
fm_receiver.span, | ||
"this expression returning a `std::io::Lines` may produce an infinite number 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,29 @@ | ||
// run-rustfix | ||
|
||
#![allow(unused, clippy::map_identity)] | ||
#![warn(clippy::lines_filter_map_ok)] | ||
|
||
use std::io::{self, BufRead, BufReader}; | ||
|
||
fn main() -> io::Result<()> { | ||
let f = std::fs::File::open("/")?; | ||
// Lint | ||
BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); | ||
// Lint | ||
let f = std::fs::File::open("/")?; | ||
BufReader::new(f).lines().map_while(Result::ok).for_each(|_| ()); | ||
let s = "foo\nbar\nbaz\n"; | ||
// Lint | ||
io::stdin().lines().map_while(Result::ok).for_each(|_| ()); | ||
// Lint | ||
io::stdin().lines().map_while(Result::ok).for_each(|_| ()); | ||
// Do not lint (not a `Lines` iterator) | ||
io::stdin() | ||
.lines() | ||
.map(std::convert::identity) | ||
.filter_map(|x| x.ok()) | ||
.for_each(|_| ()); | ||
// Do not lint (not a `Result::ok()` extractor) | ||
io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); | ||
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 @@ | ||
// run-rustfix | ||
|
||
#![allow(unused, clippy::map_identity)] | ||
#![warn(clippy::lines_filter_map_ok)] | ||
|
||
use std::io::{self, BufRead, BufReader}; | ||
|
||
fn main() -> io::Result<()> { | ||
let f = std::fs::File::open("/")?; | ||
// Lint | ||
BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ||
// Lint | ||
let f = std::fs::File::open("/")?; | ||
BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); | ||
let s = "foo\nbar\nbaz\n"; | ||
// Lint | ||
io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ||
// Lint | ||
io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ||
// Do not lint (not a `Lines` iterator) | ||
io::stdin() | ||
.lines() | ||
.map(std::convert::identity) | ||
.filter_map(|x| x.ok()) | ||
.for_each(|_| ()); | ||
// Do not lint (not a `Result::ok()` extractor) | ||
io::stdin().lines().filter_map(|x| x.err()).for_each(|_| ()); | ||
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,51 @@ | ||
error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` | ||
--> $DIR/lines_filter_map_ok.rs:11:31 | ||
| | ||
LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:11:5 | ||
| | ||
LL | BufReader::new(f).lines().filter_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
= note: `-D clippy::lines-filter-map-ok` implied by `-D warnings` | ||
|
||
error: `flat_map()` will run forever if the iterator repeatedly produces an `Err` | ||
--> $DIR/lines_filter_map_ok.rs:14:31 | ||
| | ||
LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:14:5 | ||
| | ||
LL | BufReader::new(f).lines().flat_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` | ||
--> $DIR/lines_filter_map_ok.rs:17:25 | ||
| | ||
LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:17:5 | ||
| | ||
LL | io::stdin().lines().filter_map(Result::ok).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: `filter_map()` will run forever if the iterator repeatedly produces an `Err` | ||
--> $DIR/lines_filter_map_ok.rs:19:25 | ||
| | ||
LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `map_while(Result::ok)` | ||
| | ||
note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error | ||
--> $DIR/lines_filter_map_ok.rs:19:5 | ||
| | ||
LL | io::stdin().lines().filter_map(|x| x.ok()).for_each(|_| ()); | ||
| ^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: aborting due to 4 previous errors | ||
|