]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unused_io_amount.rs
Auto merge of #93165 - eholk:disable-generator-drop-tracking, r=nikomatsakis
[rust.git] / src / tools / clippy / clippy_lints / src / unused_io_amount.rs
1 use clippy_utils::diagnostics::{span_lint, span_lint_and_help};
2 use clippy_utils::{is_try, match_trait_method, paths};
3 use rustc_hir as hir;
4 use rustc_lint::{LateContext, LateLintPass};
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6
7 declare_clippy_lint! {
8     /// ### What it does
9     /// Checks for unused written/read amount.
10     ///
11     /// ### Why is this bad?
12     /// `io::Write::write(_vectored)` and
13     /// `io::Read::read(_vectored)` are not guaranteed to
14     /// process the entire buffer. They return how many bytes were processed, which
15     /// might be smaller
16     /// than a given buffer's length. If you don't need to deal with
17     /// partial-write/read, use
18     /// `write_all`/`read_exact` instead.
19     ///
20     /// When working with asynchronous code (either with the `futures`
21     /// crate or with `tokio`), a similar issue exists for
22     /// `AsyncWriteExt::write()` and `AsyncReadExt::read()` : these
23     /// functions are also not guaranteed to process the entire
24     /// buffer.  Your code should either handle partial-writes/reads, or
25     /// call the `write_all`/`read_exact` methods on those traits instead.
26     ///
27     /// ### Known problems
28     /// Detects only common patterns.
29     ///
30     /// ### Examples
31     /// ```rust,ignore
32     /// use std::io;
33     /// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
34     ///     // must be `w.write_all(b"foo")?;`
35     ///     w.write(b"foo")?;
36     ///     Ok(())
37     /// }
38     /// ```
39     #[clippy::version = "pre 1.29.0"]
40     pub UNUSED_IO_AMOUNT,
41     correctness,
42     "unused written/read amount"
43 }
44
45 declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
46
47 impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
48     fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
49         let expr = match s.kind {
50             hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr) => expr,
51             _ => return,
52         };
53
54         match expr.kind {
55             hir::ExprKind::Match(res, _, _) if is_try(cx, expr).is_some() => {
56                 if let hir::ExprKind::Call(func, [ref arg_0, ..]) = res.kind {
57                     if matches!(
58                         func.kind,
59                         hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
60                     ) {
61                         check_map_error(cx, arg_0, expr);
62                     }
63                 } else {
64                     check_map_error(cx, res, expr);
65                 }
66             },
67             hir::ExprKind::MethodCall(path, [ref arg_0, ..], _) => match path.ident.as_str() {
68                 "expect" | "unwrap" | "unwrap_or" | "unwrap_or_else" => {
69                     check_map_error(cx, arg_0, expr);
70                 },
71                 _ => (),
72             },
73             _ => (),
74         }
75     }
76 }
77
78 /// If `expr` is an (e).await, return the inner expression "e" that's being
79 /// waited on.  Otherwise return None.
80 fn try_remove_await<'a>(expr: &'a hir::Expr<'a>) -> Option<&hir::Expr<'a>> {
81     if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
82         if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
83             if matches!(
84                 func.kind,
85                 hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
86             ) {
87                 return Some(arg_0);
88             }
89         }
90     }
91
92     None
93 }
94
95 fn check_map_error(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>) {
96     let mut call = call;
97     while let hir::ExprKind::MethodCall(path, args, _) = call.kind {
98         if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
99             call = &args[0];
100         } else {
101             break;
102         }
103     }
104
105     if let Some(call) = try_remove_await(call) {
106         check_method_call(cx, call, expr, true);
107     } else {
108         check_method_call(cx, call, expr, false);
109     }
110 }
111
112 fn check_method_call(cx: &LateContext<'_>, call: &hir::Expr<'_>, expr: &hir::Expr<'_>, is_await: bool) {
113     if let hir::ExprKind::MethodCall(path, _, _) = call.kind {
114         let symbol = path.ident.as_str();
115         let read_trait = if is_await {
116             match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
117                 || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT)
118         } else {
119             match_trait_method(cx, call, &paths::IO_READ)
120         };
121         let write_trait = if is_await {
122             match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT)
123                 || match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
124         } else {
125             match_trait_method(cx, call, &paths::IO_WRITE)
126         };
127
128         match (read_trait, write_trait, symbol, is_await) {
129             (true, _, "read", false) => span_lint_and_help(
130                 cx,
131                 UNUSED_IO_AMOUNT,
132                 expr.span,
133                 "read amount is not handled",
134                 None,
135                 "use `Read::read_exact` instead, or handle partial reads",
136             ),
137             (true, _, "read", true) => span_lint_and_help(
138                 cx,
139                 UNUSED_IO_AMOUNT,
140                 expr.span,
141                 "read amount is not handled",
142                 None,
143                 "use `AsyncReadExt::read_exact` instead, or handle partial reads",
144             ),
145             (true, _, "read_vectored", _) => {
146                 span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "read amount is not handled");
147             },
148             (_, true, "write", false) => span_lint_and_help(
149                 cx,
150                 UNUSED_IO_AMOUNT,
151                 expr.span,
152                 "written amount is not handled",
153                 None,
154                 "use `Write::write_all` instead, or handle partial writes",
155             ),
156             (_, true, "write", true) => span_lint_and_help(
157                 cx,
158                 UNUSED_IO_AMOUNT,
159                 expr.span,
160                 "written amount is not handled",
161                 None,
162                 "use `AsyncWriteExt::write_all` instead, or handle partial writes",
163             ),
164             (_, true, "write_vectored", _) => {
165                 span_lint(cx, UNUSED_IO_AMOUNT, expr.span, "written amount is not handled");
166             },
167             _ => (),
168         }
169     }
170 }