1 use clippy_config
::types
::DisallowedPath
;
2 use clippy_utils
::diagnostics
::span_lint_and_then
;
3 use clippy_utils
::{match_def_path, paths}
;
4 use rustc_data_structures
::fx
::FxHashMap
;
6 use rustc_hir
::def_id
::DefId
;
7 use rustc_lint
::{LateContext, LateLintPass}
;
8 use rustc_middle
::mir
::CoroutineLayout
;
9 use rustc_session
::impl_lint_pass
;
10 use rustc_span
::{sym, Span}
;
12 declare_clippy_lint
! {
14 /// Checks for calls to await while holding a non-async-aware MutexGuard.
16 /// ### Why is this bad?
17 /// The Mutex types found in std::sync and parking_lot
18 /// are not designed to operate in an async context across await points.
20 /// There are two potential solutions. One is to use an async-aware Mutex
21 /// type. Many asynchronous foundation crates provide such a Mutex type. The
22 /// other solution is to ensure the mutex is unlocked before calling await,
23 /// either by introducing a scope or an explicit call to Drop::drop.
25 /// ### Known problems
26 /// Will report false positive for explicitly dropped guards
27 /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
28 /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
32 /// # use std::sync::Mutex;
33 /// # async fn baz() {}
34 /// async fn foo(x: &Mutex<u32>) {
35 /// let mut guard = x.lock().unwrap();
40 /// async fn bar(x: &Mutex<u32>) {
41 /// let mut guard = x.lock().unwrap();
43 /// drop(guard); // explicit drop
50 /// # use std::sync::Mutex;
51 /// # async fn baz() {}
52 /// async fn foo(x: &Mutex<u32>) {
54 /// let mut guard = x.lock().unwrap();
60 /// async fn bar(x: &Mutex<u32>) {
62 /// let mut guard = x.lock().unwrap();
64 /// } // guard dropped here at end of scope
68 #[clippy::version = "1.45.0"]
69 pub AWAIT_HOLDING_LOCK
,
71 "inside an async function, holding a `MutexGuard` while calling `await`"
74 declare_clippy_lint
! {
76 /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
78 /// ### Why is this bad?
79 /// `RefCell` refs only check for exclusive mutable access
80 /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
81 /// risks panics from a mutable ref shared while other refs are outstanding.
83 /// ### Known problems
84 /// Will report false positive for explicitly dropped refs
85 /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
86 /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
90 /// # use std::cell::RefCell;
91 /// # async fn baz() {}
92 /// async fn foo(x: &RefCell<u32>) {
93 /// let mut y = x.borrow_mut();
98 /// async fn bar(x: &RefCell<u32>) {
99 /// let mut y = x.borrow_mut();
101 /// drop(y); // explicit drop
108 /// # use std::cell::RefCell;
109 /// # async fn baz() {}
110 /// async fn foo(x: &RefCell<u32>) {
112 /// let mut y = x.borrow_mut();
118 /// async fn bar(x: &RefCell<u32>) {
120 /// let mut y = x.borrow_mut();
122 /// } // y dropped here at end of scope
126 #[clippy::version = "1.49.0"]
127 pub AWAIT_HOLDING_REFCELL_REF
,
129 "inside an async function, holding a `RefCell` ref while calling `await`"
132 declare_clippy_lint
! {
134 /// Allows users to configure types which should not be held across `await`
135 /// suspension points.
137 /// ### Why is this bad?
138 /// There are some types which are perfectly "safe" to be used concurrently
139 /// from a memory access perspective but will cause bugs at runtime if they
140 /// are held in such a way.
145 /// await-holding-invalid-types = [
146 /// # You can specify a type name
147 /// "CustomLockType",
148 /// # You can (optionally) specify a reason
149 /// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
154 /// # async fn baz() {}
155 /// struct CustomLockType;
156 /// struct OtherCustomLockType;
158 /// let _x = CustomLockType;
159 /// let _y = OtherCustomLockType;
160 /// baz().await; // Lint violation
163 #[clippy::version = "1.62.0"]
164 pub AWAIT_HOLDING_INVALID_TYPE
,
166 "holding a type across an await point which is not allowed to be held as per the configuration"
169 impl_lint_pass
!(AwaitHolding
=> [AWAIT_HOLDING_LOCK
, AWAIT_HOLDING_REFCELL_REF
, AWAIT_HOLDING_INVALID_TYPE
]);
172 pub struct AwaitHolding
{
173 conf_invalid_types
: Vec
<DisallowedPath
>,
174 def_ids
: FxHashMap
<DefId
, DisallowedPath
>,
178 pub(crate) fn new(conf_invalid_types
: Vec
<DisallowedPath
>) -> Self {
181 def_ids
: FxHashMap
::default(),
186 impl<'tcx
> LateLintPass
<'tcx
> for AwaitHolding
{
187 fn check_crate(&mut self, cx
: &LateContext
<'tcx
>) {
188 for conf
in &self.conf_invalid_types
{
189 let segs
: Vec
<_
> = conf
.path().split("::").collect();
190 for id
in clippy_utils
::def_path_def_ids(cx
, &segs
) {
191 self.def_ids
.insert(id
, conf
.clone());
196 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx hir
::Expr
<'tcx
>) {
197 if let hir
::ExprKind
::Closure(hir
::Closure
{
198 kind
: hir
::ClosureKind
::Coroutine(hir
::CoroutineKind
::Desugared(hir
::CoroutineDesugaring
::Async
, _
)),
203 if let Some(coroutine_layout
) = cx
.tcx
.mir_coroutine_witnesses(*def_id
) {
204 self.check_interior_types(cx
, coroutine_layout
);
211 fn check_interior_types(&self, cx
: &LateContext
<'_
>, coroutine
: &CoroutineLayout
<'_
>) {
212 for (ty_index
, ty_cause
) in coroutine
.field_tys
.iter_enumerated() {
213 if let rustc_middle
::ty
::Adt(adt
, _
) = ty_cause
.ty
.kind() {
214 let await_points
= || {
218 .filter_map(|(variant
, source_info
)| {
219 coroutine
.variant_fields
[variant
]
222 .then_some(source_info
.span
)
226 if is_mutex_guard(cx
, adt
.did()) {
230 ty_cause
.source_info
.span
,
231 "this `MutexGuard` is held across an `await` point",
234 "consider using an async-aware `Mutex` type or ensuring the \
235 `MutexGuard` is dropped before calling await",
239 "these are all the `await` points this lock is held through",
243 } else if is_refcell_ref(cx
, adt
.did()) {
246 AWAIT_HOLDING_REFCELL_REF
,
247 ty_cause
.source_info
.span
,
248 "this `RefCell` reference is held across an `await` point",
250 diag
.help("ensure the reference is dropped before calling `await`");
253 "these are all the `await` points this reference is held through",
257 } else if let Some(disallowed
) = self.def_ids
.get(&adt
.did()) {
258 emit_invalid_type(cx
, ty_cause
.source_info
.span
, disallowed
);
265 fn emit_invalid_type(cx
: &LateContext
<'_
>, span
: Span
, disallowed
: &DisallowedPath
) {
268 AWAIT_HOLDING_INVALID_TYPE
,
271 "`{}` may not be held across an `await` point per `clippy.toml`",
275 if let Some(reason
) = disallowed
.reason() {
282 fn is_mutex_guard(cx
: &LateContext
<'_
>, def_id
: DefId
) -> bool
{
283 cx
.tcx
.is_diagnostic_item(sym
::MutexGuard
, def_id
)
284 || cx
.tcx
.is_diagnostic_item(sym
::RwLockReadGuard
, def_id
)
285 || cx
.tcx
.is_diagnostic_item(sym
::RwLockWriteGuard
, def_id
)
286 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_MUTEX_GUARD
)
287 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_RWLOCK_READ_GUARD
)
288 || match_def_path(cx
, def_id
, &paths
::PARKING_LOT_RWLOCK_WRITE_GUARD
)
291 fn is_refcell_ref(cx
: &LateContext
<'_
>, def_id
: DefId
) -> bool
{
293 cx
.tcx
.get_diagnostic_name(def_id
),
294 Some(sym
::RefCellRef
| sym
::RefCellRefMut
)