1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::{match_def_path, paths};
3 use rustc_hir::def_id::DefId;
4 use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::ty::GeneratorInteriorTypeCause;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
10 declare_clippy_lint! {
12 /// Checks for calls to await while holding a non-async-aware MutexGuard.
14 /// ### Why is this bad?
15 /// The Mutex types found in std::sync and parking_lot
16 /// are not designed to operate in an async context across await points.
18 /// There are two potential solutions. One is to use an async-aware Mutex
19 /// type. Many asynchronous foundation crates provide such a Mutex type. The
20 /// other solution is to ensure the mutex is unlocked before calling await,
21 /// either by introducing a scope or an explicit call to Drop::drop.
23 /// ### Known problems
24 /// Will report false positive for explicitly dropped guards
25 /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
26 /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
30 /// # use std::sync::Mutex;
31 /// # async fn baz() {}
32 /// async fn foo(x: &Mutex<u32>) {
33 /// let mut guard = x.lock().unwrap();
38 /// async fn bar(x: &Mutex<u32>) {
39 /// let mut guard = x.lock().unwrap();
41 /// drop(guard); // explicit drop
48 /// # use std::sync::Mutex;
49 /// # async fn baz() {}
50 /// async fn foo(x: &Mutex<u32>) {
52 /// let mut guard = x.lock().unwrap();
58 /// async fn bar(x: &Mutex<u32>) {
60 /// let mut guard = x.lock().unwrap();
62 /// } // guard dropped here at end of scope
66 #[clippy::version = "1.45.0"]
67 pub AWAIT_HOLDING_LOCK,
69 "inside an async function, holding a `MutexGuard` while calling `await`"
72 declare_clippy_lint! {
74 /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
76 /// ### Why is this bad?
77 /// `RefCell` refs only check for exclusive mutable access
78 /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
79 /// risks panics from a mutable ref shared while other refs are outstanding.
81 /// ### Known problems
82 /// Will report false positive for explicitly dropped refs
83 /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
84 /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
88 /// # use std::cell::RefCell;
89 /// # async fn baz() {}
90 /// async fn foo(x: &RefCell<u32>) {
91 /// let mut y = x.borrow_mut();
96 /// async fn bar(x: &RefCell<u32>) {
97 /// let mut y = x.borrow_mut();
99 /// drop(y); // explicit drop
106 /// # use std::cell::RefCell;
107 /// # async fn baz() {}
108 /// async fn foo(x: &RefCell<u32>) {
110 /// let mut y = x.borrow_mut();
116 /// async fn bar(x: &RefCell<u32>) {
118 /// let mut y = x.borrow_mut();
120 /// } // y dropped here at end of scope
124 #[clippy::version = "1.49.0"]
125 pub AWAIT_HOLDING_REFCELL_REF,
127 "inside an async function, holding a `RefCell` ref while calling `await`"
130 declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
132 impl LateLintPass<'_> for AwaitHolding {
133 fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
134 use AsyncGeneratorKind::{Block, Closure, Fn};
135 if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
136 let body_id = BodyId {
137 hir_id: body.value.hir_id,
139 let typeck_results = cx.tcx.typeck_body(body_id);
140 check_interior_types(
142 typeck_results.generator_interior_types.as_ref().skip_binder(),
149 fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
150 for ty_cause in ty_causes {
151 if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
152 if is_mutex_guard(cx, adt.did()) {
157 "this `MutexGuard` is held across an `await` point",
160 "consider using an async-aware `Mutex` type or ensuring the \
161 `MutexGuard` is dropped before calling await",
164 ty_cause.scope_span.unwrap_or(span),
165 "these are all the `await` points this lock is held through",
170 if is_refcell_ref(cx, adt.did()) {
173 AWAIT_HOLDING_REFCELL_REF,
175 "this `RefCell` reference is held across an `await` point",
177 diag.help("ensure the reference is dropped before calling `await`");
179 ty_cause.scope_span.unwrap_or(span),
180 "these are all the `await` points this reference is held through",
189 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
190 match_def_path(cx, def_id, &paths::MUTEX_GUARD)
191 || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
192 || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
193 || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
194 || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
195 || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
198 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
199 match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)