From 5e3812b6801b26a20d8ab83b6074c37766395ed9 Mon Sep 17 00:00:00 2001 From: Chengxu Bian Date: Fri, 27 Jun 2025 22:22:20 -0400 Subject: [PATCH] fix inf loop --- clippy_lints/src/loops/infinite_loop.rs | 45 ++++++++++++++-- tests/ui/infinite_loops.rs | 71 +++++++++++++++++++++++++ tests/ui/infinite_loops.stderr | 24 ++++++++- 3 files changed, 135 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index 797ff1f39866..a8bb31c507d5 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -4,7 +4,7 @@ use hir::intravisit::{Visitor, walk_expr}; use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind}; use rustc_ast::Label; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::{self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind}; use rustc_lint::{LateContext, LintContext}; use rustc_span::sym; @@ -29,6 +29,10 @@ pub(super) fn check<'tcx>( return; } + if is_inside_unawaited_async_block(cx, expr) { + return; + } + if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) { return; } @@ -60,6 +64,39 @@ pub(super) fn check<'tcx>( } } +/// Check if the given expression is inside an async block that is not being awaited. +/// This helps avoid false positives when async blocks are spawned or assigned to variables. +fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let current_hir_id = expr.hir_id; + for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) { + if let Node::Expr(Expr { + kind: + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)), + .. + }), + .. + }) = parent_node + { + return !is_async_block_awaited(cx, expr); + } + } + false +} + +fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool { + for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) { + if let Node::Expr(Expr { + kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar), + .. + }) = parent_node + { + return true; + } + } + false +} + fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) { match parent_node { @@ -67,8 +104,8 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty. Node::Expr(Expr { kind: - ExprKind::Closure(hir::Closure { - kind: hir::ClosureKind::Coroutine(_), + ExprKind::Closure(Closure { + kind: ClosureKind::Coroutine(_), .. }), .. @@ -90,7 +127,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option .. }) | Node::Expr(Expr { - kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }), + kind: ExprKind::Closure(Closure { fn_decl: decl, .. }), .. }) => return Some(decl.output), _ => (), diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index fcd1f795fff0..9b8c39331970 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -450,4 +450,75 @@ mod issue_12338 { } } +#[allow(clippy::let_underscore_future, clippy::empty_loop)] +mod issue_14000 { + use super::do_something; + + async fn foo() { + let _ = async move { + loop { + //~^ infinite_loop + do_something(); + } + } + .await; + let _ = async move { + loop { + //~^ infinite_loop + continue; + } + } + .await; + } + + fn bar() { + let _ = async move { + loop { + do_something(); + } + }; + + let _ = async move { + loop { + continue; + } + }; + } +} + +#[allow(clippy::let_underscore_future)] +mod tokio_spawn_test { + use super::do_something; + + fn install_ticker() { + // This should NOT trigger the lint because the async block is spawned, not awaited + std::thread::spawn(move || { + async move { + loop { + // This loop should not trigger infinite_loop lint + do_something(); + } + } + }); + } + + fn spawn_async_block() { + // This should NOT trigger the lint because the async block is not awaited + let _handle = async move { + loop { + do_something(); + } + }; + } + + fn await_async_block() { + // This SHOULD trigger the lint because the async block is awaited + let _ = async move { + loop { + do_something(); + } + }; + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index 4d02636ef4ae..4c6b6f725f13 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return LL | fn continue_outer() -> ! { | ++++ -error: aborting due to 21 previous errors +error: infinite loop detected + --> tests/ui/infinite_loops.rs:459:13 + | +LL | / loop { +LL | | +LL | | do_something(); +LL | | } + | |_____________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: infinite loop detected + --> tests/ui/infinite_loops.rs:466:13 + | +LL | / loop { +LL | | +LL | | continue; +LL | | } + | |_____________^ + | + = help: if this is not intended, try adding a `break` or `return` condition in the loop + +error: aborting due to 23 previous errors