1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::{match_def_path, paths}
;
3 use rustc_data_structures
::fx
::FxHashMap
;
4 use rustc_hir
::def_id
::DefId
;
5 use rustc_hir
::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind}
;
6 use rustc_lint
::{LateContext, LateLintPass}
;
7 use rustc_middle
::ty
::GeneratorInteriorTypeCause
;
8 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
11 use crate::utils
::conf
::DisallowedType
;
13 declare_clippy_lint
! {
15 /// Checks for calls to await while holding a non-async-aware MutexGuard.
17 /// ### Why is this bad?
18 /// The Mutex types found in std::sync and parking_lot
19 /// are not designed to operate in an async context across await points.
21 /// There are two potential solutions. One is to use an async-aware Mutex
22 /// type. Many asynchronous foundation crates provide such a Mutex type. The
23 /// other solution is to ensure the mutex is unlocked before calling await,
24 /// either by introducing a scope or an explicit call to Drop::drop.
26 /// ### Known problems
27 /// Will report false positive for explicitly dropped guards
28 /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
29 /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
33 /// # use std::sync::Mutex;
34 /// # async fn baz() {}
35 /// async fn foo(x: &Mutex<u32>) {
36 /// let mut guard = x.lock().unwrap();
41 /// async fn bar(x: &Mutex<u32>) {
42 /// let mut guard = x.lock().unwrap();
44 /// drop(guard); // explicit drop
51 /// # use std::sync::Mutex;
52 /// # async fn baz() {}
53 /// async fn foo(x: &Mutex<u32>) {
55 /// let mut guard = x.lock().unwrap();
61 /// async fn bar(x: &Mutex<u32>) {
63 /// let mut guard = x.lock().unwrap();
65 /// } // guard dropped here at end of scope
69 #[clippy::version = "1.45.0"]
70 pub AWAIT_HOLDING_LOCK
,
72 "inside an async function, holding a `MutexGuard` while calling `await`"
75 declare_clippy_lint
! {
77 /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
79 /// ### Why is this bad?
80 /// `RefCell` refs only check for exclusive mutable access
81 /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
82 /// risks panics from a mutable ref shared while other refs are outstanding.
84 /// ### Known problems
85 /// Will report false positive for explicitly dropped refs
86 /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
87 /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
91 /// # use std::cell::RefCell;
92 /// # async fn baz() {}
93 /// async fn foo(x: &RefCell<u32>) {
94 /// let mut y = x.borrow_mut();
99 /// async fn bar(x: &RefCell<u32>) {
100 /// let mut y = x.borrow_mut();
102 /// drop(y); // explicit drop
109 /// # use std::cell::RefCell;
110 /// # async fn baz() {}
111 /// async fn foo(x: &RefCell<u32>) {
113 /// let mut y = x.borrow_mut();
119 /// async fn bar(x: &RefCell<u32>) {
121 /// let mut y = x.borrow_mut();
123 /// } // y dropped here at end of scope
127 #[clippy::version = "1.49.0"]
128 pub AWAIT_HOLDING_REFCELL_REF
,
130 "inside an async function, holding a `RefCell` ref while calling `await`"
133 declare_clippy_lint
! {
135 /// Allows users to configure types which should not be held across `await`
136 /// suspension points.
138 /// ### Why is this bad?
139 /// There are some types which are perfectly "safe" to be used concurrently
140 /// from a memory access perspective but will cause bugs at runtime if they
141 /// are held in such a way.
146 /// await-holding-invalid-types = [
147 /// # You can specify a type name
148 /// "CustomLockType",
149 /// # You can (optionally) specify a reason
150 /// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
155 /// # async fn baz() {}
156 /// struct CustomLockType;
157 /// struct OtherCustomLockType;
159 /// let _x = CustomLockType;
160 /// let _y = OtherCustomLockType;
161 /// baz().await; // Lint violation
164 #[clippy::version = "1.49.0"]
165 pub AWAIT_HOLDING_INVALID_TYPE
,
167 "holding a type across an await point which is not allowed to be held as per the configuration"
170 impl_lint_pass
!(AwaitHolding
=> [AWAIT_HOLDING_LOCK
, AWAIT_HOLDING_REFCELL_REF
, AWAIT_HOLDING_INVALID_TYPE
]);
173 pub struct AwaitHolding
{
174 conf_invalid_types
: Vec
<DisallowedType
>,
175 def_ids
: FxHashMap
<DefId
, DisallowedType
>,
179 pub(crate) fn new(conf_invalid_types
: Vec
<DisallowedType
>) -> Self {
182 def_ids
: FxHashMap
::default(),
187 impl LateLintPass
<'_
> for AwaitHolding
{
188 fn check_crate(&mut self, cx
: &LateContext
<'_
>) {
189 for conf
in &self.conf_invalid_types
{
190 let path
= match conf
{
191 DisallowedType
::Simple(path
) | DisallowedType
::WithReason { path, .. }
=> path
,
193 let segs
: Vec
<_
> = path
.split("::").collect();
194 if let Res
::Def(_
, id
) = clippy_utils
::def_path_res(cx
, &segs
) {
195 self.def_ids
.insert(id
, conf
.clone());
200 fn check_body(&mut self, cx
: &LateContext
<'_
>, body
: &'_ Body
<'_
>) {
201 use AsyncGeneratorKind
::{Block, Closure, Fn}
;
202 if let Some(GeneratorKind
::Async(Block
| Closure
| Fn
)) = body
.generator_kind
{
203 let body_id
= BodyId
{
204 hir_id
: body
.value
.hir_id
,
206 let typeck_results
= cx
.tcx
.typeck_body(body_id
);
207 self.check_interior_types(
209 typeck_results
.generator_interior_types
.as_ref().skip_binder(),
217 fn check_interior_types(&self, cx
: &LateContext
<'_
>, ty_causes
: &[GeneratorInteriorTypeCause
<'_
>], span
: Span
) {
218 for ty_cause
in ty_causes
{
219 if let rustc_middle
::ty
::Adt(adt
, _
) = ty_cause
.ty
.kind() {
220 if is_mutex_guard(cx
, adt
.did()) {
225 "this `MutexGuard` is held across an `await` point",
228 "consider using an async-aware `Mutex` type or ensuring the \
229 `MutexGuard` is dropped before calling await",
232 ty_cause
.scope_span
.unwrap_or(span
),
233 "these are all the `await` points this lock is held through",
237 } else if is_refcell_ref(cx
, adt
.did()) {
240 AWAIT_HOLDING_REFCELL_REF
,
242 "this `RefCell` reference is held across an `await` point",
244 diag
.help("ensure the reference is dropped before calling `await`");
246 ty_cause
.scope_span
.unwrap_or(span
),
247 "these are all the `await` points this reference is held through",
251 } else if let Some(disallowed
) = self.def_ids
.get(&adt
.did()) {
252 emit_invalid_type(cx
, ty_cause
.span
, disallowed
);
259 fn emit_invalid_type(cx
: &LateContext
<'_
>, span
: Span
, disallowed
: &DisallowedType
) {
260 let (type_name
, reason
) = match disallowed
{
261 DisallowedType
::Simple(path
) => (path
, &None
),
262 DisallowedType
::WithReason { path, reason }
=> (path
, reason
),
267 AWAIT_HOLDING_INVALID_TYPE
,
269 &format
!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
271 if let Some(reason
) = reason
{
272 diag
.note(reason
.clone());
278 fn is_mutex_guard(cx
: &LateContext
<'_
>, def_id
: DefId
) -> bool
{
279 match_def_path(cx
, def_id
, &paths
::MUTEX_GUARD
)
280 || match_def_path(cx
, def_id
, &paths
::RWLOCK_READ_GUARD
)
281 || match_def_path(cx
, def_id
, &paths
::RWLOCK_WRITE_GUARD
)
282 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_MUTEX_GUARD
)
283 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_RWLOCK_READ_GUARD
)
284 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_RWLOCK_WRITE_GUARD
)
287 fn is_refcell_ref(cx
: &LateContext
<'_
>, def_id
: DefId
) -> bool
{
288 match_def_path(cx
, def_id
, &paths
::REFCELL_REF
) || match_def_path(cx
, def_id
, &paths
::REFCELL_REFMUT
)