[[infinite_loop] fix infinite loop false positive (#15157)
changelog: [infinite_loop]: Improve handling of infinite loops in async blocks Fix rust-lang/rust-clippy#14000 This PR refines the [infinite_loop] lint to avoid false positives when infinite loops occur inside async blocks that are not awaited (such as those that are spawned or assigned to variables for later use). The lint will now only trigger when the async block containing the loop is directly awaited.
This commit is contained in:
commit
386372a0e4
3 changed files with 137 additions and 6 deletions
|
|
@ -1,10 +1,11 @@
|
|||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed};
|
||||
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, Expr, ExprKind, FnRetTy, FnSig, Node, TyKind,
|
||||
};
|
||||
use rustc_lint::{LateContext, LintContext};
|
||||
use rustc_span::sym;
|
||||
|
||||
|
|
@ -29,6 +30,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 +65,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<FnRetTy<'tcx>> {
|
||||
for (_, parent_node) in cx.tcx.hir_parent_iter(expr.hir_id) {
|
||||
match parent_node {
|
||||
|
|
@ -67,8 +105,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 +128,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),
|
||||
_ => (),
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue