2024-03-09 10:09:17 -06:00
|
|
|
use clippy_utils::diagnostics::span_lint_hir_and_then;
|
2024-01-30 14:41:26 -06:00
|
|
|
use clippy_utils::macros::{is_panic, root_macro_call_first_node};
|
|
|
|
use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks};
|
2024-03-09 10:09:17 -06:00
|
|
|
use hir::{ExprKind, HirId, PatKind};
|
2020-01-06 10:39:50 -06:00
|
|
|
use rustc_hir as hir;
|
2020-01-12 00:08:41 -06:00
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
2023-11-25 11:45:27 -06:00
|
|
|
use rustc_session::declare_lint_pass;
|
2023-12-21 05:26:46 -06:00
|
|
|
use rustc_span::{sym, Span};
|
2017-01-07 05:35:45 -06:00
|
|
|
|
2018-03-28 08:24:26 -05:00
|
|
|
declare_clippy_lint! {
|
2021-07-29 05:16:06 -05:00
|
|
|
/// ### What it does
|
|
|
|
/// Checks for unused written/read amount.
|
2019-03-05 10:50:33 -06:00
|
|
|
///
|
2021-07-29 05:16:06 -05:00
|
|
|
/// ### Why is this bad?
|
|
|
|
/// `io::Write::write(_vectored)` and
|
2020-01-09 08:46:55 -06:00
|
|
|
/// `io::Read::read(_vectored)` are not guaranteed to
|
2019-03-05 10:50:33 -06:00
|
|
|
/// process the entire buffer. They return how many bytes were processed, which
|
|
|
|
/// might be smaller
|
|
|
|
/// than a given buffer's length. If you don't need to deal with
|
|
|
|
/// partial-write/read, use
|
|
|
|
/// `write_all`/`read_exact` instead.
|
|
|
|
///
|
2022-01-13 06:18:19 -06:00
|
|
|
/// When working with asynchronous code (either with the `futures`
|
|
|
|
/// crate or with `tokio`), a similar issue exists for
|
|
|
|
/// `AsyncWriteExt::write()` and `AsyncReadExt::read()` : these
|
|
|
|
/// functions are also not guaranteed to process the entire
|
|
|
|
/// buffer. Your code should either handle partial-writes/reads, or
|
|
|
|
/// call the `write_all`/`read_exact` methods on those traits instead.
|
|
|
|
///
|
2021-07-29 05:16:06 -05:00
|
|
|
/// ### Known problems
|
|
|
|
/// Detects only common patterns.
|
2019-03-05 10:50:33 -06:00
|
|
|
///
|
2022-01-13 06:18:19 -06:00
|
|
|
/// ### Examples
|
2019-03-05 10:50:33 -06:00
|
|
|
/// ```rust,ignore
|
|
|
|
/// use std::io;
|
|
|
|
/// fn foo<W: io::Write>(w: &mut W) -> io::Result<()> {
|
|
|
|
/// // must be `w.write_all(b"foo")?;`
|
|
|
|
/// w.write(b"foo")?;
|
|
|
|
/// Ok(())
|
|
|
|
/// }
|
|
|
|
/// ```
|
2021-12-06 05:33:31 -06:00
|
|
|
#[clippy::version = "pre 1.29.0"]
|
2017-01-07 05:35:45 -06:00
|
|
|
pub UNUSED_IO_AMOUNT,
|
2018-03-28 08:24:26 -05:00
|
|
|
correctness,
|
2017-01-07 05:35:45 -06:00
|
|
|
"unused written/read amount"
|
|
|
|
}
|
|
|
|
|
2019-04-08 15:43:55 -05:00
|
|
|
declare_lint_pass!(UnusedIoAmount => [UNUSED_IO_AMOUNT]);
|
2017-01-07 05:35:45 -06:00
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
enum IoOp {
|
|
|
|
AsyncWrite(bool),
|
|
|
|
AsyncRead(bool),
|
|
|
|
SyncRead(bool),
|
|
|
|
SyncWrite(bool),
|
|
|
|
}
|
|
|
|
|
2020-06-25 15:41:36 -05:00
|
|
|
impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount {
|
2023-12-21 05:26:46 -06:00
|
|
|
/// We perform the check on the block level.
|
|
|
|
/// If we want to catch match and if expressions that act as returns of the block
|
|
|
|
/// we need to check them at `check_expr` or `check_block` as they are not stmts
|
|
|
|
/// but we can't check them at `check_expr` because we need the broader context
|
|
|
|
/// because we should do this only for the final expression of the block, and not for
|
|
|
|
/// `StmtKind::Local` which binds values => the io amount is used.
|
|
|
|
///
|
|
|
|
/// To check for unused io amount in stmts, we only consider `StmtKind::Semi`.
|
|
|
|
/// `StmtKind::Local` is not considered because it binds values => the io amount is used.
|
|
|
|
/// `StmtKind::Expr` is not considered because requires unit type => the io amount is used.
|
|
|
|
/// `StmtKind::Item` is not considered because it's not an expression.
|
|
|
|
///
|
|
|
|
/// We then check the individual expressions via `check_expr`. We use the same logic for
|
|
|
|
/// semi expressions and the final expression as we need to check match and if expressions
|
|
|
|
/// for binding of the io amount to `Ok(_)`.
|
|
|
|
///
|
|
|
|
/// We explicitly check for the match source to be Normal as it needs special logic
|
|
|
|
/// to consider the arms, and we want to avoid breaking the logic for situations where things
|
|
|
|
/// get desugared to match.
|
|
|
|
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) {
|
|
|
|
for stmt in block.stmts {
|
|
|
|
if let hir::StmtKind::Semi(exp) = stmt.kind {
|
|
|
|
check_expr(cx, exp);
|
|
|
|
}
|
|
|
|
}
|
2017-01-07 05:35:45 -06:00
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
if let Some(exp) = block.expr
|
2024-01-30 14:41:26 -06:00
|
|
|
&& matches!(
|
|
|
|
exp.kind,
|
|
|
|
hir::ExprKind::If(_, _, _) | hir::ExprKind::Match(_, _, hir::MatchSource::Normal)
|
|
|
|
)
|
2023-12-21 05:26:46 -06:00
|
|
|
{
|
|
|
|
check_expr(cx, exp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:41:26 -06:00
|
|
|
fn non_consuming_err_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
|
|
|
// if there is a guard, we consider the result to be consumed
|
|
|
|
if arm.guard.is_some() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if is_unreachable_or_panic(cx, arm.body) {
|
|
|
|
// if the body is unreachable or there is a panic,
|
|
|
|
// we consider the result to be consumed
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let PatKind::TupleStruct(ref path, [inner_pat], _) = arm.pat.kind {
|
|
|
|
return is_res_lang_ctor(cx, cx.qpath_res(path, inner_pat.hir_id), hir::LangItem::ResultErr);
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
fn non_consuming_ok_arm<'a>(cx: &LateContext<'a>, arm: &hir::Arm<'a>) -> bool {
|
|
|
|
// if there is a guard, we consider the result to be consumed
|
|
|
|
if arm.guard.is_some() {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if is_unreachable_or_panic(cx, arm.body) {
|
|
|
|
// if the body is unreachable or there is a panic,
|
|
|
|
// we consider the result to be consumed
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if is_ok_wild_or_dotdot_pattern(cx, arm.pat) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
fn check_expr<'a>(cx: &LateContext<'a>, expr: &'a hir::Expr<'a>) {
|
|
|
|
match expr.kind {
|
|
|
|
hir::ExprKind::If(cond, _, _)
|
|
|
|
if let ExprKind::Let(hir::Let { pat, init, .. }) = cond.kind
|
2024-01-30 14:41:26 -06:00
|
|
|
&& is_ok_wild_or_dotdot_pattern(cx, pat)
|
2023-12-21 05:26:46 -06:00
|
|
|
&& let Some(op) = should_lint(cx, init) =>
|
|
|
|
{
|
2024-03-09 10:09:17 -06:00
|
|
|
emit_lint(cx, cond.span, cond.hir_id, op, &[pat.span]);
|
2023-12-21 05:26:46 -06:00
|
|
|
},
|
2024-01-30 14:41:26 -06:00
|
|
|
// we will capture only the case where the match is Ok( ) or Err( )
|
|
|
|
// prefer to match the minimum possible, and expand later if needed
|
|
|
|
// to avoid false positives on something as used as this
|
|
|
|
hir::ExprKind::Match(expr, [arm1, arm2], hir::MatchSource::Normal) if let Some(op) = should_lint(cx, expr) => {
|
|
|
|
if non_consuming_ok_arm(cx, arm1) && non_consuming_err_arm(cx, arm2) {
|
2024-03-09 10:09:17 -06:00
|
|
|
emit_lint(cx, expr.span, expr.hir_id, op, &[arm1.pat.span]);
|
2024-01-30 14:41:26 -06:00
|
|
|
}
|
|
|
|
if non_consuming_ok_arm(cx, arm2) && non_consuming_err_arm(cx, arm1) {
|
2024-03-09 10:09:17 -06:00
|
|
|
emit_lint(cx, expr.span, expr.hir_id, op, &[arm2.pat.span]);
|
2023-12-21 05:26:46 -06:00
|
|
|
}
|
|
|
|
},
|
2024-01-30 14:41:26 -06:00
|
|
|
hir::ExprKind::Match(_, _, hir::MatchSource::Normal) => {},
|
2023-12-21 05:26:46 -06:00
|
|
|
_ if let Some(op) = should_lint(cx, expr) => {
|
2024-03-09 10:09:17 -06:00
|
|
|
emit_lint(cx, expr.span, expr.hir_id, op, &[]);
|
2023-12-21 05:26:46 -06:00
|
|
|
},
|
|
|
|
_ => {},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn should_lint<'a>(cx: &LateContext<'a>, mut inner: &'a hir::Expr<'a>) -> Option<IoOp> {
|
|
|
|
inner = unpack_match(inner);
|
|
|
|
inner = unpack_try(inner);
|
|
|
|
inner = unpack_call_chain(inner);
|
|
|
|
inner = unpack_await(inner);
|
|
|
|
// we type-check it to get whether it's a read/write or their vectorized forms
|
|
|
|
// and keep only the ones that are produce io amount
|
|
|
|
check_io_mode(cx, inner)
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:41:26 -06:00
|
|
|
fn is_ok_wild_or_dotdot_pattern<'a>(cx: &LateContext<'a>, pat: &hir::Pat<'a>) -> bool {
|
2023-12-21 05:26:46 -06:00
|
|
|
// the if checks whether we are in a result Ok( ) pattern
|
|
|
|
// and the return checks whether it is unhandled
|
|
|
|
|
2024-01-30 14:41:26 -06:00
|
|
|
if let PatKind::TupleStruct(ref path, inner_pat, _) = pat.kind
|
2023-12-21 05:26:46 -06:00
|
|
|
// we check against Result::Ok to avoid linting on Err(_) or something else.
|
|
|
|
&& is_res_lang_ctor(cx, cx.qpath_res(path, pat.hir_id), hir::LangItem::ResultOk)
|
|
|
|
{
|
2024-01-30 14:41:26 -06:00
|
|
|
if matches!(inner_pat, []) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if let [cons_pat] = inner_pat
|
|
|
|
&& matches!(cons_pat.kind, PatKind::Wild)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2023-12-21 05:26:46 -06:00
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2024-01-30 14:41:26 -06:00
|
|
|
// this is partially taken from panic_unimplemented
|
|
|
|
fn is_unreachable_or_panic(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> bool {
|
|
|
|
let expr = peel_blocks(expr);
|
|
|
|
let Some(macro_call) = root_macro_call_first_node(cx, expr) else {
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
if is_panic(cx, macro_call.def_id) {
|
|
|
|
return !cx.tcx.hir().is_inside_const_context(expr.hir_id);
|
|
|
|
}
|
|
|
|
matches!(cx.tcx.item_name(macro_call.def_id).as_str(), "unreachable")
|
|
|
|
}
|
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
|
|
|
while let hir::ExprKind::MethodCall(path, receiver, ..) = expr.kind {
|
|
|
|
if matches!(
|
|
|
|
path.ident.as_str(),
|
|
|
|
"unwrap" | "expect" | "unwrap_or" | "unwrap_or_else" | "ok" | "is_ok" | "is_err" | "or_else" | "or"
|
|
|
|
) {
|
|
|
|
expr = receiver;
|
|
|
|
} else {
|
|
|
|
break;
|
2017-01-07 05:35:45 -06:00
|
|
|
}
|
|
|
|
}
|
2023-12-21 05:26:46 -06:00
|
|
|
expr
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
|
|
|
while let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind
|
|
|
|
&& matches!(
|
|
|
|
func.kind,
|
|
|
|
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..))
|
|
|
|
)
|
|
|
|
{
|
|
|
|
expr = arg_0;
|
|
|
|
}
|
|
|
|
expr
|
|
|
|
}
|
|
|
|
|
|
|
|
fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> {
|
|
|
|
while let hir::ExprKind::Match(res, _, _) = expr.kind {
|
|
|
|
expr = res;
|
|
|
|
}
|
|
|
|
expr
|
2017-01-07 05:35:45 -06:00
|
|
|
}
|
|
|
|
|
2022-01-13 06:18:19 -06:00
|
|
|
/// If `expr` is an (e).await, return the inner expression "e" that's being
|
|
|
|
/// waited on. Otherwise return None.
|
2023-12-21 05:26:46 -06:00
|
|
|
fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &hir::Expr<'a> {
|
2022-01-13 06:18:19 -06:00
|
|
|
if let hir::ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind {
|
|
|
|
if let hir::ExprKind::Call(func, [ref arg_0, ..]) = expr.kind {
|
|
|
|
if matches!(
|
|
|
|
func.kind,
|
|
|
|
hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..))
|
|
|
|
) {
|
2023-12-21 05:26:46 -06:00
|
|
|
return arg_0;
|
2022-01-13 06:18:19 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-12-21 05:26:46 -06:00
|
|
|
expr
|
2022-01-13 06:18:19 -06:00
|
|
|
}
|
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
/// Check whether the current expr is a function call for an IO operation
|
|
|
|
fn check_io_mode(cx: &LateContext<'_>, call: &hir::Expr<'_>) -> Option<IoOp> {
|
|
|
|
let hir::ExprKind::MethodCall(path, ..) = call.kind else {
|
|
|
|
return None;
|
|
|
|
};
|
2022-01-13 06:18:19 -06:00
|
|
|
|
2023-12-21 05:26:46 -06:00
|
|
|
let vectorized = match path.ident.as_str() {
|
|
|
|
"write_vectored" | "read_vectored" => true,
|
|
|
|
"write" | "read" => false,
|
|
|
|
_ => {
|
|
|
|
return None;
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
match (
|
|
|
|
is_trait_method(cx, call, sym::IoRead),
|
|
|
|
is_trait_method(cx, call, sym::IoWrite),
|
|
|
|
match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCREADEXT)
|
|
|
|
|| match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCREADEXT),
|
|
|
|
match_trait_method(cx, call, &paths::TOKIO_IO_ASYNCWRITEEXT)
|
|
|
|
|| match_trait_method(cx, call, &paths::FUTURES_IO_ASYNCWRITEEXT),
|
|
|
|
) {
|
|
|
|
(true, _, _, _) => Some(IoOp::SyncRead(vectorized)),
|
|
|
|
(_, true, _, _) => Some(IoOp::SyncWrite(vectorized)),
|
|
|
|
(_, _, true, _) => Some(IoOp::AsyncRead(vectorized)),
|
|
|
|
(_, _, _, true) => Some(IoOp::AsyncWrite(vectorized)),
|
|
|
|
_ => None,
|
2022-01-13 06:18:19 -06:00
|
|
|
}
|
2021-04-27 09:55:11 -05:00
|
|
|
}
|
|
|
|
|
2024-03-09 10:09:17 -06:00
|
|
|
fn emit_lint(cx: &LateContext<'_>, span: Span, at: HirId, op: IoOp, wild_cards: &[Span]) {
|
2023-12-21 05:26:46 -06:00
|
|
|
let (msg, help) = match op {
|
|
|
|
IoOp::AsyncRead(false) => (
|
|
|
|
"read amount is not handled",
|
|
|
|
Some("use `AsyncReadExt::read_exact` instead, or handle partial reads"),
|
|
|
|
),
|
|
|
|
IoOp::SyncRead(false) => (
|
|
|
|
"read amount is not handled",
|
|
|
|
Some("use `Read::read_exact` instead, or handle partial reads"),
|
|
|
|
),
|
|
|
|
IoOp::SyncWrite(false) => (
|
|
|
|
"written amount is not handled",
|
|
|
|
Some("use `Write::write_all` instead, or handle partial writes"),
|
|
|
|
),
|
|
|
|
IoOp::AsyncWrite(false) => (
|
|
|
|
"written amount is not handled",
|
|
|
|
Some("use `AsyncWriteExt::write_all` instead, or handle partial writes"),
|
|
|
|
),
|
|
|
|
IoOp::SyncRead(true) | IoOp::AsyncRead(true) => ("read amount is not handled", None),
|
|
|
|
IoOp::SyncWrite(true) | IoOp::AsyncWrite(true) => ("written amount is not handled", None),
|
|
|
|
};
|
2020-01-09 08:46:55 -06:00
|
|
|
|
2024-03-09 10:09:17 -06:00
|
|
|
span_lint_hir_and_then(cx, UNUSED_IO_AMOUNT, at, span, msg, |diag| {
|
2023-12-21 05:26:46 -06:00
|
|
|
if let Some(help_str) = help {
|
|
|
|
diag.help(help_str);
|
2017-01-07 05:35:45 -06:00
|
|
|
}
|
2023-12-21 05:26:46 -06:00
|
|
|
for span in wild_cards {
|
|
|
|
diag.span_note(
|
|
|
|
*span,
|
|
|
|
"the result is consumed here, but the amount of I/O bytes remains unhandled",
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
2017-01-07 05:35:45 -06:00
|
|
|
}
|