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::{Namespace, Res};
5 use rustc_hir::def_id::DefId;
6 use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_middle::ty::GeneratorInteriorTypeCause;
9 use rustc_session::{declare_tool_lint, impl_lint_pass};
10 use rustc_span::{sym, Span};
12 use crate::utils::conf::DisallowedPath;
14 declare_clippy_lint! {
16 /// Checks for calls to await while holding a non-async-aware MutexGuard.
18 /// ### Why is this bad?
19 /// The Mutex types found in std::sync and parking_lot
20 /// are not designed to operate in an async context across await points.
22 /// There are two potential solutions. One is to use an async-aware Mutex
23 /// type. Many asynchronous foundation crates provide such a Mutex type. The
24 /// other solution is to ensure the mutex is unlocked before calling await,
25 /// either by introducing a scope or an explicit call to Drop::drop.
27 /// ### Known problems
28 /// Will report false positive for explicitly dropped guards
29 /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
30 /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
34 /// # use std::sync::Mutex;
35 /// # async fn baz() {}
36 /// async fn foo(x: &Mutex<u32>) {
37 /// let mut guard = x.lock().unwrap();
42 /// async fn bar(x: &Mutex<u32>) {
43 /// let mut guard = x.lock().unwrap();
45 /// drop(guard); // explicit drop
52 /// # use std::sync::Mutex;
53 /// # async fn baz() {}
54 /// async fn foo(x: &Mutex<u32>) {
56 /// let mut guard = x.lock().unwrap();
62 /// async fn bar(x: &Mutex<u32>) {
64 /// let mut guard = x.lock().unwrap();
66 /// } // guard dropped here at end of scope
70 #[clippy::version = "1.45.0"]
71 pub AWAIT_HOLDING_LOCK,
73 "inside an async function, holding a `MutexGuard` while calling `await`"
76 declare_clippy_lint! {
78 /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
80 /// ### Why is this bad?
81 /// `RefCell` refs only check for exclusive mutable access
82 /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
83 /// risks panics from a mutable ref shared while other refs are outstanding.
85 /// ### Known problems
86 /// Will report false positive for explicitly dropped refs
87 /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
88 /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
92 /// # use std::cell::RefCell;
93 /// # async fn baz() {}
94 /// async fn foo(x: &RefCell<u32>) {
95 /// let mut y = x.borrow_mut();
100 /// async fn bar(x: &RefCell<u32>) {
101 /// let mut y = x.borrow_mut();
103 /// drop(y); // explicit drop
110 /// # use std::cell::RefCell;
111 /// # async fn baz() {}
112 /// async fn foo(x: &RefCell<u32>) {
114 /// let mut y = x.borrow_mut();
120 /// async fn bar(x: &RefCell<u32>) {
122 /// let mut y = x.borrow_mut();
124 /// } // y dropped here at end of scope
128 #[clippy::version = "1.49.0"]
129 pub AWAIT_HOLDING_REFCELL_REF,
131 "inside an async function, holding a `RefCell` ref while calling `await`"
134 declare_clippy_lint! {
136 /// Allows users to configure types which should not be held across `await`
137 /// suspension points.
139 /// ### Why is this bad?
140 /// There are some types which are perfectly "safe" to be used concurrently
141 /// from a memory access perspective but will cause bugs at runtime if they
142 /// are held in such a way.
147 /// await-holding-invalid-types = [
148 /// # You can specify a type name
149 /// "CustomLockType",
150 /// # You can (optionally) specify a reason
151 /// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
156 /// # async fn baz() {}
157 /// struct CustomLockType;
158 /// struct OtherCustomLockType;
160 /// let _x = CustomLockType;
161 /// let _y = OtherCustomLockType;
162 /// baz().await; // Lint violation
165 #[clippy::version = "1.62.0"]
166 pub AWAIT_HOLDING_INVALID_TYPE,
168 "holding a type across an await point which is not allowed to be held as per the configuration"
171 impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
174 pub struct AwaitHolding {
175 conf_invalid_types: Vec<DisallowedPath>,
176 def_ids: FxHashMap<DefId, DisallowedPath>,
180 pub(crate) fn new(conf_invalid_types: Vec<DisallowedPath>) -> Self {
183 def_ids: FxHashMap::default(),
188 impl LateLintPass<'_> for AwaitHolding {
189 fn check_crate(&mut self, cx: &LateContext<'_>) {
190 for conf in &self.conf_invalid_types {
191 let segs: Vec<_> = conf.path().split("::").collect();
192 if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs, Some(Namespace::TypeNS)) {
193 self.def_ids.insert(id, conf.clone());
198 fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
199 use AsyncGeneratorKind::{Block, Closure, Fn};
200 if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
201 let body_id = BodyId {
202 hir_id: body.value.hir_id,
204 let typeck_results = cx.tcx.typeck_body(body_id);
205 self.check_interior_types(
207 typeck_results.generator_interior_types.as_ref().skip_binder(),
215 fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
216 for ty_cause in ty_causes {
217 if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
218 if is_mutex_guard(cx, adt.did()) {
223 "this `MutexGuard` is held across an `await` point",
226 "consider using an async-aware `Mutex` type or ensuring the \
227 `MutexGuard` is dropped before calling await",
230 ty_cause.scope_span.unwrap_or(span),
231 "these are all the `await` points this lock is held through",
235 } else if is_refcell_ref(cx, adt.did()) {
238 AWAIT_HOLDING_REFCELL_REF,
240 "this `RefCell` reference is held across an `await` point",
242 diag.help("ensure the reference is dropped before calling `await`");
244 ty_cause.scope_span.unwrap_or(span),
245 "these are all the `await` points this reference is held through",
249 } else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
250 emit_invalid_type(cx, ty_cause.span, disallowed);
257 fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedPath) {
260 AWAIT_HOLDING_INVALID_TYPE,
263 "`{}` may not be held across an `await` point per `clippy.toml`",
267 if let Some(reason) = disallowed.reason() {
274 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
275 cx.tcx.is_diagnostic_item(sym::MutexGuard, def_id)
276 || cx.tcx.is_diagnostic_item(sym::RwLockReadGuard, def_id)
277 || cx.tcx.is_diagnostic_item(sym::RwLockWriteGuard, def_id)
278 || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
279 || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
280 || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
283 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
284 match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)