2022-02-26 13:26:21 +00:00
|
|
|
use clippy_utils::diagnostics::span_lint_and_then;
|
2021-03-25 18:29:11 +00:00
|
|
|
use clippy_utils::{match_def_path, paths};
|
2022-04-14 23:52:10 +00:00
|
|
|
use rustc_data_structures::fx::FxHashMap;
|
2020-04-10 04:50:23 +00:00
|
|
|
use rustc_hir::def_id::DefId;
|
2022-04-14 23:52:10 +00:00
|
|
|
use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
|
2020-04-08 04:20:37 +00:00
|
|
|
use rustc_lint::{LateContext, LateLintPass};
|
2020-04-17 06:21:49 +00:00
|
|
|
use rustc_middle::ty::GeneratorInteriorTypeCause;
|
2022-04-14 23:52:10 +00:00
|
|
|
use rustc_session::{declare_tool_lint, impl_lint_pass};
|
2020-04-10 04:50:23 +00:00
|
|
|
use rustc_span::Span;
|
2020-04-08 04:20:37 +00:00
|
|
|
|
2022-04-14 23:52:10 +00:00
|
|
|
use crate::utils::conf::DisallowedType;
|
|
|
|
|
2020-04-08 04:20:37 +00:00
|
|
|
declare_clippy_lint! {
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### What it does
|
2022-02-26 13:26:21 +00:00
|
|
|
/// Checks for calls to await while holding a non-async-aware MutexGuard.
|
2020-04-08 04:20:37 +00:00
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Why is this bad?
|
|
|
|
/// The Mutex types found in std::sync and parking_lot
|
2020-07-14 12:59:59 +00:00
|
|
|
/// are not designed to operate in an async context across await points.
|
2020-04-08 04:20:37 +00:00
|
|
|
///
|
2021-10-07 09:21:30 +00:00
|
|
|
/// There are two potential solutions. One is to use an async-aware Mutex
|
2020-04-17 06:21:49 +00:00
|
|
|
/// type. Many asynchronous foundation crates provide such a Mutex type. The
|
|
|
|
/// other solution is to ensure the mutex is unlocked before calling await,
|
|
|
|
/// either by introducing a scope or an explicit call to Drop::drop.
|
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Known problems
|
2022-02-26 13:26:21 +00:00
|
|
|
/// Will report false positive for explicitly dropped guards
|
|
|
|
/// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
|
|
|
|
/// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
|
2020-04-08 04:20:37 +00:00
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Example
|
2022-02-26 13:26:21 +00:00
|
|
|
/// ```rust
|
|
|
|
/// # use std::sync::Mutex;
|
|
|
|
/// # async fn baz() {}
|
2020-04-08 04:20:37 +00:00
|
|
|
/// async fn foo(x: &Mutex<u32>) {
|
2022-02-26 13:26:21 +00:00
|
|
|
/// let mut guard = x.lock().unwrap();
|
2020-04-08 04:20:37 +00:00
|
|
|
/// *guard += 1;
|
2022-02-26 13:26:21 +00:00
|
|
|
/// baz().await;
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// async fn bar(x: &Mutex<u32>) {
|
|
|
|
/// let mut guard = x.lock().unwrap();
|
|
|
|
/// *guard += 1;
|
|
|
|
/// drop(guard); // explicit drop
|
|
|
|
/// baz().await;
|
2020-04-08 04:20:37 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2020-04-17 06:21:49 +00:00
|
|
|
///
|
2020-04-08 04:20:37 +00:00
|
|
|
/// Use instead:
|
2022-02-26 13:26:21 +00:00
|
|
|
/// ```rust
|
|
|
|
/// # use std::sync::Mutex;
|
|
|
|
/// # async fn baz() {}
|
2020-04-08 04:20:37 +00:00
|
|
|
/// async fn foo(x: &Mutex<u32>) {
|
|
|
|
/// {
|
2022-02-26 13:26:21 +00:00
|
|
|
/// let mut guard = x.lock().unwrap();
|
2020-04-08 04:20:37 +00:00
|
|
|
/// *guard += 1;
|
|
|
|
/// }
|
2022-02-26 13:26:21 +00:00
|
|
|
/// baz().await;
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// async fn bar(x: &Mutex<u32>) {
|
|
|
|
/// {
|
|
|
|
/// let mut guard = x.lock().unwrap();
|
|
|
|
/// *guard += 1;
|
|
|
|
/// } // guard dropped here at end of scope
|
|
|
|
/// baz().await;
|
2020-04-08 04:20:37 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2021-12-06 11:33:31 +00:00
|
|
|
#[clippy::version = "1.45.0"]
|
2020-04-08 04:20:37 +00:00
|
|
|
pub AWAIT_HOLDING_LOCK,
|
2022-02-26 13:26:21 +00:00
|
|
|
suspicious,
|
|
|
|
"inside an async function, holding a `MutexGuard` while calling `await`"
|
2020-04-08 04:20:37 +00:00
|
|
|
}
|
|
|
|
|
2020-10-28 22:36:07 +00:00
|
|
|
declare_clippy_lint! {
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### What it does
|
2022-02-26 13:26:21 +00:00
|
|
|
/// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
|
2020-10-28 22:36:07 +00:00
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Why is this bad?
|
|
|
|
/// `RefCell` refs only check for exclusive mutable access
|
2020-10-28 22:36:07 +00:00
|
|
|
/// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
|
|
|
|
/// risks panics from a mutable ref shared while other refs are outstanding.
|
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Known problems
|
2022-02-26 13:26:21 +00:00
|
|
|
/// Will report false positive for explicitly dropped refs
|
|
|
|
/// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
|
|
|
|
/// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
|
2020-10-28 22:36:07 +00:00
|
|
|
///
|
2021-07-29 10:16:06 +00:00
|
|
|
/// ### Example
|
2022-02-26 13:26:21 +00:00
|
|
|
/// ```rust
|
|
|
|
/// # use std::cell::RefCell;
|
|
|
|
/// # async fn baz() {}
|
2020-10-28 22:36:07 +00:00
|
|
|
/// async fn foo(x: &RefCell<u32>) {
|
2020-11-23 12:51:04 +00:00
|
|
|
/// let mut y = x.borrow_mut();
|
|
|
|
/// *y += 1;
|
2022-02-26 13:26:21 +00:00
|
|
|
/// baz().await;
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// async fn bar(x: &RefCell<u32>) {
|
|
|
|
/// let mut y = x.borrow_mut();
|
|
|
|
/// *y += 1;
|
|
|
|
/// drop(y); // explicit drop
|
|
|
|
/// baz().await;
|
2020-10-28 22:36:07 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// Use instead:
|
2022-02-26 13:26:21 +00:00
|
|
|
/// ```rust
|
|
|
|
/// # use std::cell::RefCell;
|
|
|
|
/// # async fn baz() {}
|
2020-10-28 22:36:07 +00:00
|
|
|
/// async fn foo(x: &RefCell<u32>) {
|
|
|
|
/// {
|
2020-11-23 12:51:04 +00:00
|
|
|
/// let mut y = x.borrow_mut();
|
|
|
|
/// *y += 1;
|
2020-10-28 22:36:07 +00:00
|
|
|
/// }
|
2022-02-26 13:26:21 +00:00
|
|
|
/// baz().await;
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// async fn bar(x: &RefCell<u32>) {
|
|
|
|
/// {
|
|
|
|
/// let mut y = x.borrow_mut();
|
|
|
|
/// *y += 1;
|
|
|
|
/// } // y dropped here at end of scope
|
|
|
|
/// baz().await;
|
2020-10-28 22:36:07 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
2021-12-06 11:33:31 +00:00
|
|
|
#[clippy::version = "1.49.0"]
|
2020-10-28 22:36:07 +00:00
|
|
|
pub AWAIT_HOLDING_REFCELL_REF,
|
2022-02-26 13:26:21 +00:00
|
|
|
suspicious,
|
|
|
|
"inside an async function, holding a `RefCell` ref while calling `await`"
|
2020-10-28 22:36:07 +00:00
|
|
|
}
|
2020-04-08 04:20:37 +00:00
|
|
|
|
2022-04-14 23:52:10 +00:00
|
|
|
declare_clippy_lint! {
|
|
|
|
/// ### What it does
|
|
|
|
/// Allows users to configure types which should not be held across `await`
|
|
|
|
/// suspension points.
|
|
|
|
///
|
|
|
|
/// ### Why is this bad?
|
|
|
|
/// There are some types which are perfectly "safe" to be used concurrently
|
|
|
|
/// from a memory access perspective but will cause bugs at runtime if they
|
|
|
|
/// are held in such a way.
|
|
|
|
///
|
|
|
|
/// ### Known problems
|
|
|
|
///
|
|
|
|
/// ### Example
|
|
|
|
///
|
2022-04-15 21:54:19 +00:00
|
|
|
/// Strings are, of course, safe to hold across await points. This example
|
|
|
|
/// exists only to show how this lint might be configured for third-party
|
|
|
|
/// crates.
|
2022-04-14 23:52:10 +00:00
|
|
|
///
|
|
|
|
/// ```toml
|
|
|
|
/// await-holding-invalid-types = [
|
2022-04-15 21:54:19 +00:00
|
|
|
/// "std::string::String",
|
2022-04-14 23:52:10 +00:00
|
|
|
/// ]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # async fn baz() {}
|
|
|
|
/// async fn foo() {
|
2022-04-15 21:54:19 +00:00
|
|
|
/// let _x = String::from("hello!");
|
|
|
|
/// baz().await; // Lint violation
|
2022-04-14 23:52:10 +00:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
#[clippy::version = "1.49.0"]
|
|
|
|
pub AWAIT_HOLDING_INVALID_TYPE,
|
|
|
|
suspicious,
|
2022-04-18 17:31:57 +00:00
|
|
|
"holding a type across an await point which is not allowed to be held as per the configuration"
|
2022-04-14 23:52:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct AwaitHolding {
|
|
|
|
conf_invalid_types: Vec<DisallowedType>,
|
|
|
|
def_ids: FxHashMap<DefId, DisallowedType>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl AwaitHolding {
|
|
|
|
pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
|
|
|
|
Self {
|
|
|
|
conf_invalid_types,
|
|
|
|
def_ids: FxHashMap::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-28 22:36:07 +00:00
|
|
|
|
|
|
|
impl LateLintPass<'_> for AwaitHolding {
|
2022-04-14 23:52:10 +00:00
|
|
|
fn check_crate(&mut self, cx: &LateContext<'_>) {
|
|
|
|
for conf in &self.conf_invalid_types {
|
|
|
|
let path = match conf {
|
|
|
|
DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
|
|
|
|
};
|
|
|
|
let segs: Vec<_> = path.split("::").collect();
|
|
|
|
if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
|
|
|
|
self.def_ids.insert(id, conf.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-25 20:41:36 +00:00
|
|
|
fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
|
2020-04-17 06:21:49 +00:00
|
|
|
use AsyncGeneratorKind::{Block, Closure, Fn};
|
2020-06-09 14:36:01 +00:00
|
|
|
if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
|
|
|
|
let body_id = BodyId {
|
|
|
|
hir_id: body.value.hir_id,
|
|
|
|
};
|
2021-03-12 14:30:50 +00:00
|
|
|
let typeck_results = cx.tcx.typeck_body(body_id);
|
2022-04-14 23:52:10 +00:00
|
|
|
self.check_interior_types(
|
2020-12-20 16:19:49 +00:00
|
|
|
cx,
|
2021-04-08 15:50:13 +00:00
|
|
|
typeck_results.generator_interior_types.as_ref().skip_binder(),
|
2020-12-20 16:19:49 +00:00
|
|
|
body.value.span,
|
|
|
|
);
|
2020-04-08 04:20:37 +00:00
|
|
|
}
|
2020-04-17 06:21:49 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-08 04:20:37 +00:00
|
|
|
|
2022-04-14 23:52:10 +00:00
|
|
|
impl AwaitHolding {
|
|
|
|
fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
|
|
|
|
for ty_cause in ty_causes {
|
|
|
|
if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
|
|
|
|
if is_mutex_guard(cx, adt.did()) {
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
AWAIT_HOLDING_LOCK,
|
|
|
|
ty_cause.span,
|
|
|
|
"this `MutexGuard` is held across an `await` point",
|
|
|
|
|diag| {
|
|
|
|
diag.help(
|
|
|
|
"consider using an async-aware `Mutex` type or ensuring the \
|
2022-02-26 13:26:21 +00:00
|
|
|
`MutexGuard` is dropped before calling await",
|
2022-04-14 23:52:10 +00:00
|
|
|
);
|
|
|
|
diag.span_note(
|
|
|
|
ty_cause.scope_span.unwrap_or(span),
|
|
|
|
"these are all the `await` points this lock is held through",
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} else if is_refcell_ref(cx, adt.did()) {
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
AWAIT_HOLDING_REFCELL_REF,
|
|
|
|
ty_cause.span,
|
|
|
|
"this `RefCell` reference is held across an `await` point",
|
|
|
|
|diag| {
|
|
|
|
diag.help("ensure the reference is dropped before calling `await`");
|
|
|
|
diag.span_note(
|
|
|
|
ty_cause.scope_span.unwrap_or(span),
|
|
|
|
"these are all the `await` points this reference is held through",
|
|
|
|
);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
|
|
|
|
emit_invalid_type(cx, ty_cause.span, disallowed);
|
|
|
|
}
|
2020-10-28 22:36:07 +00:00
|
|
|
}
|
2020-04-08 04:20:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-14 23:52:10 +00:00
|
|
|
fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
|
|
|
|
let (type_name, reason) = match disallowed {
|
|
|
|
DisallowedType::Simple(path) => (path, &None),
|
|
|
|
DisallowedType::WithReason { path, reason } => (path, reason),
|
|
|
|
};
|
|
|
|
|
|
|
|
span_lint_and_then(
|
|
|
|
cx,
|
|
|
|
AWAIT_HOLDING_INVALID_TYPE,
|
|
|
|
span,
|
2022-04-18 17:32:03 +00:00
|
|
|
&format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
|
2022-04-14 23:52:10 +00:00
|
|
|
|diag| {
|
|
|
|
if let Some(reason) = reason {
|
2022-04-18 17:32:11 +00:00
|
|
|
diag.note(reason.clone());
|
2022-04-14 23:52:10 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-06-25 20:41:36 +00:00
|
|
|
fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
2020-04-10 04:50:23 +00:00
|
|
|
match_def_path(cx, def_id, &paths::MUTEX_GUARD)
|
|
|
|
|| match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
|
|
|
|
|| match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
|
|
|
|
|| match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
|
|
|
|
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
|
|
|
|
|| match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
|
2020-04-08 04:20:37 +00:00
|
|
|
}
|
2020-10-28 22:36:07 +00:00
|
|
|
|
|
|
|
fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
|
|
|
|
match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
|
|
|
|
}
|