Skip to content

Commit 5a37f7b

Browse files
authored
Fix map_entry FP when it would cause MutexGuard to be held across an (#16199)
await point. Closes: #16173 ---- changelog: [`map_entry`] fix FP when it would cause `MutexGuard` to be held across an await point.
2 parents c59e7a9 + c788c7c commit 5a37f7b

File tree

3 files changed

+85
-11
lines changed

3 files changed

+85
-11
lines changed

clippy_lints/src/entry.rs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ use clippy_utils::source::{reindent_multiline, snippet_indent, snippet_with_appl
33
use clippy_utils::ty::is_copy;
44
use clippy_utils::visitors::for_each_expr;
55
use clippy_utils::{
6-
SpanlessEq, can_move_expr_to_closure_no_visit, higher, is_expr_final_block_expr, is_expr_used_or_unified,
7-
peel_hir_expr_while,
6+
SpanlessEq, can_move_expr_to_closure_no_visit, desugar_await, higher, is_expr_final_block_expr,
7+
is_expr_used_or_unified, paths, peel_hir_expr_while,
88
};
99
use core::fmt::{self, Write};
1010
use rustc_errors::Applicability;
11+
use rustc_hir::def_id::DefId;
1112
use rustc_hir::hir_id::HirIdSet;
1213
use rustc_hir::intravisit::{Visitor, walk_body, walk_expr};
1314
use rustc_hir::{Block, Expr, ExprKind, HirId, Pat, Stmt, StmtKind, UnOp};
@@ -382,6 +383,8 @@ struct InsertSearcher<'cx, 'tcx> {
382383
loops: Vec<HirId>,
383384
/// Local variables created in the expression. These don't need to be captured.
384385
locals: HirIdSet,
386+
/// Whether the map is a non-async-aware `MutexGuard`.
387+
map_is_mutex_guard: bool,
385388
}
386389
impl<'tcx> InsertSearcher<'_, 'tcx> {
387390
/// Visit the expression as a branch in control flow. Multiple insert calls can be used, but
@@ -524,15 +527,22 @@ impl<'tcx> Visitor<'tcx> for InsertSearcher<'_, 'tcx> {
524527
ExprKind::If(cond_expr, then_expr, Some(else_expr)) => {
525528
self.is_single_insert = false;
526529
self.visit_non_tail_expr(cond_expr);
527-
// Each branch may contain it's own insert expression.
530+
// Each branch may contain its own insert expression.
528531
let mut is_map_used = self.visit_cond_arm(then_expr);
529532
is_map_used |= self.visit_cond_arm(else_expr);
530533
self.is_map_used = is_map_used;
531534
},
532535
ExprKind::Match(scrutinee_expr, arms, _) => {
536+
// If the map is a non-async-aware `MutexGuard` and
537+
// `.await` expression appears alongside map insertion in the same `then` or `else` block,
538+
// we cannot suggest using `entry()` because it would hold the lock across the await point,
539+
// triggering `await_holding_lock` and risking deadlock.
540+
if self.map_is_mutex_guard && desugar_await(expr).is_some() {
541+
self.can_use_entry = false;
542+
}
533543
self.is_single_insert = false;
534544
self.visit_non_tail_expr(scrutinee_expr);
535-
// Each branch may contain it's own insert expression.
545+
// Each branch may contain its own insert expression.
536546
let mut is_map_used = self.is_map_used;
537547
for arm in arms {
538548
self.visit_pat(arm.pat);
@@ -725,16 +735,32 @@ fn find_insert_calls<'tcx>(
725735
edits: Vec::new(),
726736
loops: Vec::new(),
727737
locals: HirIdSet::default(),
738+
map_is_mutex_guard: false,
728739
};
740+
// Check if the map is a non-async-aware `MutexGuard`
741+
if let rustc_middle::ty::Adt(adt, _) = cx.typeck_results().expr_ty(contains_expr.map).kind()
742+
&& is_mutex_guard(cx, adt.did())
743+
{
744+
s.map_is_mutex_guard = true;
745+
}
746+
729747
s.visit_expr(expr);
730-
let allow_insert_closure = s.allow_insert_closure;
731-
let is_single_insert = s.is_single_insert;
748+
if !s.can_use_entry {
749+
return None;
750+
}
751+
732752
let is_key_used_and_no_copy = s.is_key_used && !is_copy(cx, cx.typeck_results().expr_ty(contains_expr.key));
733-
let edits = s.edits;
734-
s.can_use_entry.then_some(InsertSearchResults {
735-
edits,
736-
allow_insert_closure,
737-
is_single_insert,
753+
Some(InsertSearchResults {
754+
edits: s.edits,
755+
allow_insert_closure: s.allow_insert_closure,
756+
is_single_insert: s.is_single_insert,
738757
is_key_used_and_no_copy,
739758
})
740759
}
760+
761+
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
762+
match cx.tcx.get_diagnostic_name(def_id) {
763+
Some(name) => matches!(name, sym::MutexGuard | sym::RwLockReadGuard | sym::RwLockWriteGuard),
764+
None => paths::PARKING_LOT_GUARDS.iter().any(|guard| guard.matches(cx, def_id)),
765+
}
766+
}

tests/ui/entry.fixed

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,28 @@ mod issue14449 {
248248
}
249249
}
250250

251+
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
252+
mod issue_16173 {
253+
use std::collections::HashMap;
254+
use std::sync::Mutex;
255+
256+
async fn f() {}
257+
258+
async fn foo() {
259+
let mu_map = Mutex::new(HashMap::new());
260+
if !mu_map.lock().unwrap().contains_key(&0) {
261+
f().await;
262+
mu_map.lock().unwrap().insert(0, 0);
263+
}
264+
265+
if mu_map.lock().unwrap().contains_key(&1) {
266+
todo!();
267+
} else {
268+
mu_map.lock().unwrap().insert(1, 42);
269+
todo!();
270+
f().await;
271+
}
272+
}
273+
}
274+
251275
fn main() {}

tests/ui/entry.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,28 @@ mod issue14449 {
254254
}
255255
}
256256

257+
// Don't suggest when it would cause `MutexGuard` to be held across an await point.
258+
mod issue_16173 {
259+
use std::collections::HashMap;
260+
use std::sync::Mutex;
261+
262+
async fn f() {}
263+
264+
async fn foo() {
265+
let mu_map = Mutex::new(HashMap::new());
266+
if !mu_map.lock().unwrap().contains_key(&0) {
267+
f().await;
268+
mu_map.lock().unwrap().insert(0, 0);
269+
}
270+
271+
if mu_map.lock().unwrap().contains_key(&1) {
272+
todo!();
273+
} else {
274+
mu_map.lock().unwrap().insert(1, 42);
275+
todo!();
276+
f().await;
277+
}
278+
}
279+
}
280+
257281
fn main() {}

0 commit comments

Comments
 (0)