]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/unused_io_amount.rs
Rollup merge of #103443 - mucinoab:recover-colon-as-path-separetor, r=compiler-errors
[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_trait_method, 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 use rustc_span::sym;
7
8 declare_clippy_lint! {
9     /// ### What it does
10     /// Checks for unused written/read amount.
11     ///
12     /// ### Why is this bad?
13     /// `io::Write::write(_vectored)` and
14     /// `io::Read::read(_vectored)` are not guaranteed to
15     /// process the entire buffer. They return how many bytes were processed, which
16     /// might be smaller
17     /// than a given buffer's length. If you don't need to deal with
18     /// partial-write/read, use
19     /// `write_all`/`read_exact` instead.
20     ///
21     /// When working with asynchronous code (either with the `futures`
22     /// crate or with `tokio`), a similar issue exists for
23     /// `AsyncWriteExt::write()` and `AsyncReadExt::read()` : these
24     /// functions are also not guaranteed to process the entire
25     /// buffer.  Your code should either handle partial-writes/reads, or
26     /// call the `write_all`/`read_exact` methods on those traits instead.
27     ///
28     /// ### Known problems
29     /// Detects only common patterns.
30     ///
31     /// ### Examples
32     /// ```rust,ignore
33     /// use std::io;
34     /// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
35     ///     // must be `w.write_all(b"foo")?;`
36     ///     w.write(b"foo")?;
37     ///     Ok(())
38     /// }
39     /// ```
40     #[clippy::version = "pre 1.29.0"]
41     pub UNUSED_IO_AMOUNT,
42     correctness,
43     "unused written/read amount"
44 }
45
46 declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
47
48 impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
49     fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) {
50         let (hir::StmtKind::Semi(expr) | hir::StmtKind::Expr(expr)) = s.kind else {
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, 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, receiver, ..) = call.kind {
98         if matches!(path.ident.as_str(), "or" | "or_else" | "ok") {
99             call = receiver;
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             is_trait_method(cx, call, sym::IoRead)
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             is_trait_method(cx, call, sym::IoWrite)
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 }