Skip to content

Commit d6e55e9

Browse files
committed
Make lint also capture blocks and closures, adjust language to mention other mutex types
1 parent 54e7f7e commit d6e55e9

File tree

3 files changed

+94
-41
lines changed

3 files changed

+94
-41
lines changed

clippy_lints/src/await_holding_lock.rs

+41-38
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
use crate::utils::{match_def_path, paths, span_lint_and_note};
22
use rustc_hir::def_id::DefId;
3-
use rustc_hir::intravisit::FnKind;
4-
use rustc_hir::{Body, FnDecl, HirId, IsAsync};
3+
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
54
use rustc_lint::{LateContext, LateLintPass};
5+
use rustc_middle::ty::GeneratorInteriorTypeCause;
66
use rustc_session::{declare_lint_pass, declare_tool_lint};
77
use rustc_span::Span;
88

99
declare_clippy_lint! {
10-
/// **What it does:** Checks for calls to await while holding a MutexGuard.
10+
/// **What it does:** Checks for calls to await while holding a
11+
/// non-async-aware MutexGuard.
1112
///
12-
/// **Why is this bad?** This is almost certainly an error which can result
13-
/// in a deadlock because the reactor will invoke code not visible to the
14-
/// currently visible scope.
13+
/// **Why is this bad?** The Mutex types found in syd::sync and parking_lot
14+
/// are not designed to operator in an async context across await points.
1515
///
16-
/// **Known problems:** Detects only specifically named guard types:
17-
/// MutexGuard, RwLockReadGuard, and RwLockWriteGuard.
16+
/// There are two potential solutions. One is to use an asynx-aware Mutex
17+
/// type. Many asynchronous foundation crates provide such a Mutex type. The
18+
/// other solution is to ensure the mutex is unlocked before calling await,
19+
/// either by introducing a scope or an explicit call to Drop::drop.
20+
///
21+
/// **Known problems:** None.
1822
///
1923
/// **Example:**
2024
///
@@ -27,6 +31,7 @@ declare_clippy_lint! {
2731
/// bar.await;
2832
/// }
2933
/// ```
34+
///
3035
/// Use instead:
3136
/// ```rust,ignore
3237
/// use std::sync::Mutex;
@@ -47,43 +52,41 @@ declare_clippy_lint! {
4752
declare_lint_pass!(AwaitHoldingLock => [AWAIT_HOLDING_LOCK]);
4853

4954
impl LateLintPass<'_, '_> for AwaitHoldingLock {
50-
fn check_fn(
51-
&mut self,
52-
cx: &LateContext<'_, '_>,
53-
fn_kind: FnKind<'_>,
54-
_: &FnDecl<'_>,
55-
_: &Body<'_>,
56-
span: Span,
57-
_: HirId,
58-
) {
59-
if !is_async_fn(fn_kind) {
60-
return;
55+
fn check_body(&mut self, cx: &LateContext<'_, '_>, body: &'_ Body<'_>) {
56+
use AsyncGeneratorKind::{Block, Closure, Fn};
57+
match body.generator_kind {
58+
Some(GeneratorKind::Async(Block))
59+
| Some(GeneratorKind::Async(Closure))
60+
| Some(GeneratorKind::Async(Fn)) => {
61+
let body_id = BodyId {
62+
hir_id: body.value.hir_id,
63+
};
64+
let def_id = cx.tcx.hir().body_owner_def_id(body_id);
65+
let tables = cx.tcx.typeck_tables_of(def_id);
66+
check_interior_types(cx, &tables.generator_interior_types, body.value.span);
67+
},
68+
_ => {},
6169
}
70+
}
71+
}
6272

63-
for ty_cause in &cx.tables.generator_interior_types {
64-
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind {
65-
if is_mutex_guard(cx, adt.did) {
66-
span_lint_and_note(
67-
cx,
68-
AWAIT_HOLDING_LOCK,
69-
ty_cause.span,
70-
"this MutexGuard is held across an 'await' point",
71-
ty_cause.scope_span.unwrap_or(span),
72-
"these are all the await points this lock is held through",
73-
);
74-
}
73+
fn check_interior_types(cx: &LateContext<'_, '_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
74+
for ty_cause in ty_causes {
75+
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind {
76+
if is_mutex_guard(cx, adt.did) {
77+
span_lint_and_note(
78+
cx,
79+
AWAIT_HOLDING_LOCK,
80+
ty_cause.span,
81+
"this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.",
82+
ty_cause.scope_span.unwrap_or(span),
83+
"these are all the await points this lock is held through",
84+
);
7585
}
7686
}
7787
}
7888
}
7989

80-
fn is_async_fn(fn_kind: FnKind<'_>) -> bool {
81-
fn_kind.header().map_or(false, |h| match h.asyncness {
82-
IsAsync::Async => true,
83-
IsAsync::NotAsync => false,
84-
})
85-
}
86-
8790
fn is_mutex_guard(cx: &LateContext<'_, '_>, def_id: DefId) -> bool {
8891
match_def_path(cx, def_id, &paths::MUTEX_GUARD)
8992
|| match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)

tests/ui/await_holding_lock.rs

+22
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,31 @@ async fn also_bad(x: &Mutex<u32>) -> u32 {
3434
first + second + third
3535
}
3636

37+
async fn not_good(x: &Mutex<u32>) -> u32 {
38+
let first = baz().await;
39+
40+
let second = {
41+
let guard = x.lock().unwrap();
42+
baz().await
43+
};
44+
45+
let third = baz().await;
46+
47+
first + second + third
48+
}
49+
50+
fn block_bad(x: &Mutex<u32>) -> impl std::future::Future<Output = u32> + '_ {
51+
async move {
52+
let guard = x.lock().unwrap();
53+
baz().await
54+
}
55+
}
56+
3757
fn main() {
3858
let m = Mutex::new(100);
3959
good(&m);
4060
bad(&m);
4161
also_bad(&m);
62+
not_good(&m);
63+
block_bad(&m);
4264
}

tests/ui/await_holding_lock.stderr

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: this MutexGuard is held across an 'await' point
1+
error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.
22
--> $DIR/await_holding_lock.rs:7:9
33
|
44
LL | let guard = x.lock().unwrap();
@@ -13,7 +13,7 @@ LL | | baz().await
1313
LL | | }
1414
| |_^
1515

16-
error: this MutexGuard is held across an 'await' point
16+
error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.
1717
--> $DIR/await_holding_lock.rs:28:9
1818
|
1919
LL | let guard = x.lock().unwrap();
@@ -31,5 +31,33 @@ LL | | first + second + third
3131
LL | | }
3232
| |_^
3333

34-
error: aborting due to 2 previous errors
34+
error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.
35+
--> $DIR/await_holding_lock.rs:41:13
36+
|
37+
LL | let guard = x.lock().unwrap();
38+
| ^^^^^
39+
|
40+
note: these are all the await points this lock is held through
41+
--> $DIR/await_holding_lock.rs:41:9
42+
|
43+
LL | / let guard = x.lock().unwrap();
44+
LL | | baz().await
45+
LL | | };
46+
| |_____^
47+
48+
error: this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.
49+
--> $DIR/await_holding_lock.rs:52:13
50+
|
51+
LL | let guard = x.lock().unwrap();
52+
| ^^^^^
53+
|
54+
note: these are all the await points this lock is held through
55+
--> $DIR/await_holding_lock.rs:52:9
56+
|
57+
LL | / let guard = x.lock().unwrap();
58+
LL | | baz().await
59+
LL | | }
60+
| |_____^
61+
62+
error: aborting due to 4 previous errors
3563

0 commit comments

Comments
 (0)