Skip to content

Commit c945393

Browse files
committed
fix inf loop
1 parent 32a216e commit c945393

File tree

3 files changed

+137
-6
lines changed

3 files changed

+137
-6
lines changed

clippy_lints/src/loops/infinite_loop.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
22
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
33
use hir::intravisit::{Visitor, walk_expr};
4-
use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind};
54
use rustc_ast::Label;
65
use rustc_errors::Applicability;
7-
use rustc_hir as hir;
6+
use rustc_hir::{
7+
self as hir, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
8+
};
89
use rustc_lint::{LateContext, LintContext};
910
use rustc_span::sym;
1011

@@ -29,6 +30,10 @@ pub(super) fn check<'tcx>(
2930
return;
3031
}
3132

33+
if is_inside_unawaited_async_block(cx, expr) {
34+
return;
35+
}
36+
3237
if expr.span.in_external_macro(cx.sess().source_map()) || is_from_proc_macro(cx, expr) {
3338
return;
3439
}
@@ -60,15 +65,48 @@ pub(super) fn check<'tcx>(
6065
}
6166
}
6267

68+
/// Check if the given expression is inside an async block that is not being awaited.
69+
/// This helps avoid false positives when async blocks are spawned or assigned to variables.
70+
fn is_inside_unawaited_async_block(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
71+
let current_hir_id = expr.hir_id;
72+
for (_, parent_node) in cx.tcx.hir_parent_iter(current_hir_id) {
73+
if let Node::Expr(Expr {
74+
kind:
75+
ExprKind::Closure(Closure {
76+
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
77+
..
78+
}),
79+
..
80+
}) = parent_node
81+
{
82+
return !is_async_block_awaited(cx, expr);
83+
}
84+
}
85+
false
86+
}
87+
88+
fn is_async_block_awaited(cx: &LateContext<'_>, async_expr: &Expr<'_>) -> bool {
89+
for (_, parent_node) in cx.tcx.hir_parent_iter(async_expr.hir_id) {
90+
if let Node::Expr(Expr {
91+
kind: ExprKind::Match(_, _, hir::MatchSource::AwaitDesugar),
92+
..
93+
}) = parent_node
94+
{
95+
return true;
96+
}
97+
}
98+
false
99+
}
100+
63101
fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option<FnRetTy<'tcx>> {
64102
for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
65103
match parent_node {
66104
// Skip `Coroutine` closures, these are the body of `async fn`, not async closures.
67105
// This is because we still need to backtrack one parent node to get the `OpaqueDef` ty.
68106
Node::Expr(Expr {
69107
kind:
70-
ExprKind::Closure(hir::Closure {
71-
kind: hir::ClosureKind::Coroutine(_),
108+
ExprKind::Closure(Closure {
109+
kind: ClosureKind::Coroutine(_),
72110
..
73111
}),
74112
..
@@ -90,7 +128,7 @@ fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option
90128
..
91129
})
92130
| Node::Expr(Expr {
93-
kind: ExprKind::Closure(hir::Closure { fn_decl: decl, .. }),
131+
kind: ExprKind::Closure(Closure { fn_decl: decl, .. }),
94132
..
95133
}) => return Some(decl.output),
96134
_ => (),

tests/ui/infinite_loops.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,4 +450,75 @@ mod issue_12338 {
450450
}
451451
}
452452

453+
#[allow(clippy::let_underscore_future, clippy::empty_loop)]
454+
mod issue_14000 {
455+
use super::do_something;
456+
457+
async fn foo() {
458+
let _ = async move {
459+
loop {
460+
//~^ infinite_loop
461+
do_something();
462+
}
463+
}
464+
.await;
465+
let _ = async move {
466+
loop {
467+
//~^ infinite_loop
468+
continue;
469+
}
470+
}
471+
.await;
472+
}
473+
474+
fn bar() {
475+
let _ = async move {
476+
loop {
477+
do_something();
478+
}
479+
};
480+
481+
let _ = async move {
482+
loop {
483+
continue;
484+
}
485+
};
486+
}
487+
}
488+
489+
#[allow(clippy::let_underscore_future)]
490+
mod tokio_spawn_test {
491+
use super::do_something;
492+
493+
fn install_ticker() {
494+
// This should NOT trigger the lint because the async block is spawned, not awaited
495+
std::thread::spawn(move || {
496+
async move {
497+
loop {
498+
// This loop should not trigger infinite_loop lint
499+
do_something();
500+
}
501+
}
502+
});
503+
}
504+
505+
fn spawn_async_block() {
506+
// This should NOT trigger the lint because the async block is not awaited
507+
let _handle = async move {
508+
loop {
509+
do_something();
510+
}
511+
};
512+
}
513+
514+
fn await_async_block() {
515+
// This SHOULD trigger the lint because the async block is awaited
516+
let _ = async move {
517+
loop {
518+
do_something();
519+
}
520+
};
521+
}
522+
}
523+
453524
fn main() {}

tests/ui/infinite_loops.stderr

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,5 +311,27 @@ help: if this is intentional, consider specifying `!` as function return
311311
LL | fn continue_outer() -> ! {
312312
| ++++
313313

314-
error: aborting due to 21 previous errors
314+
error: infinite loop detected
315+
--> tests/ui/infinite_loops.rs:459:13
316+
|
317+
LL | / loop {
318+
LL | |
319+
LL | | do_something();
320+
LL | | }
321+
| |_____________^
322+
|
323+
= help: if this is not intended, try adding a `break` or `return` condition in the loop
324+
325+
error: infinite loop detected
326+
--> tests/ui/infinite_loops.rs:466:13
327+
|
328+
LL | / loop {
329+
LL | |
330+
LL | | continue;
331+
LL | | }
332+
| |_____________^
333+
|
334+
= help: if this is not intended, try adding a `break` or `return` condition in the loop
335+
336+
error: aborting due to 23 previous errors
315337

0 commit comments

Comments
 (0)