]> git.proxmox.com Git - rustc.git/blob - src/tools/clippy/clippy_lints/src/unused_io_amount.rs
New upstream version 1.72.1+dfsg1
[rustc.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" | "is_ok" | "is_err" => {
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 }