]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/await_holding_invalid.rs
Auto merge of #8937 - Jarcho:merge_match_passes, r=llogiq
[rust.git] / clippy_lints / src / await_holding_invalid.rs
index 112c5bb14e3596c1e23842341ecf69782452b4bd..5b7c4591504e1cbaa3710a1cad5b684b4f13efbe 100644 (file)
-use crate::utils::{match_def_path, paths, span_lint_and_note};
+use clippy_utils::diagnostics::span_lint_and_then;
+use clippy_utils::{match_def_path, paths};
+use rustc_data_structures::fx::FxHashMap;
 use rustc_hir::def_id::DefId;
-use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
+use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
 use rustc_lint::{LateContext, LateLintPass};
 use rustc_middle::ty::GeneratorInteriorTypeCause;
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::Span;
 
+use crate::utils::conf::DisallowedType;
+
 declare_clippy_lint! {
-    /// **What it does:** Checks for calls to await while holding a
-    /// non-async-aware MutexGuard.
+    /// ### What it does
+    /// Checks for calls to await while holding a non-async-aware MutexGuard.
     ///
-    /// **Why is this bad?** The Mutex types found in std::sync and parking_lot
+    /// ### Why is this bad?
+    /// The Mutex types found in std::sync and parking_lot
     /// are not designed to operate in an async context across await points.
     ///
-    /// There are two potential solutions. One is to use an asynx-aware Mutex
+    /// There are two potential solutions. One is to use an async-aware Mutex
     /// 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.
     ///
-    /// **Known problems:** Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)).
-    ///
-    /// **Example:**
-    ///
-    /// ```rust,ignore
-    /// use std::sync::Mutex;
+    /// ### Known problems
+    /// 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.
     ///
+    /// ### Example
+    /// ```rust
+    /// # use std::sync::Mutex;
+    /// # async fn baz() {}
     /// async fn foo(x: &Mutex<u32>) {
-    ///   let guard = x.lock().unwrap();
+    ///   let mut guard = x.lock().unwrap();
+    ///   *guard += 1;
+    ///   baz().await;
+    /// }
+    ///
+    /// async fn bar(x: &Mutex<u32>) {
+    ///   let mut guard = x.lock().unwrap();
     ///   *guard += 1;
-    ///   bar.await;
+    ///   drop(guard); // explicit drop
+    ///   baz().await;
     /// }
     /// ```
     ///
     /// Use instead:
-    /// ```rust,ignore
-    /// use std::sync::Mutex;
-    ///
+    /// ```rust
+    /// use std::sync::Mutex;
+    /// # async fn baz() {}
     /// async fn foo(x: &Mutex<u32>) {
     ///   {
-    ///     let guard = x.lock().unwrap();
+    ///     let mut guard = x.lock().unwrap();
     ///     *guard += 1;
     ///   }
-    ///   bar.await;
+    ///   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;
     /// }
     /// ```
+    #[clippy::version = "1.45.0"]
     pub AWAIT_HOLDING_LOCK,
-    pedantic,
-    "Inside an async function, holding a MutexGuard while calling await"
+    suspicious,
+    "inside an async function, holding a `MutexGuard` while calling `await`"
 }
 
 declare_clippy_lint! {
-    /// **What it does:** Checks for calls to await while holding a
-    /// `RefCell` `Ref` or `RefMut`.
+    /// ### What it does
+    /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
     ///
-    /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access
+    /// ### Why is this bad?
+    /// `RefCell` refs only check for exclusive mutable access
     /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
     /// risks panics from a mutable ref shared while other refs are outstanding.
     ///
-    /// **Known problems:** Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)).
-    ///
-    /// **Example:**
-    ///
-    /// ```rust,ignore
-    /// use std::cell::RefCell;
+    /// ### Known problems
+    /// 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.
     ///
+    /// ### Example
+    /// ```rust
+    /// # use std::cell::RefCell;
+    /// # async fn baz() {}
     /// async fn foo(x: &RefCell<u32>) {
     ///   let mut y = x.borrow_mut();
     ///   *y += 1;
-    ///   bar.await;
+    ///   baz().await;
+    /// }
+    ///
+    /// async fn bar(x: &RefCell<u32>) {
+    ///   let mut y = x.borrow_mut();
+    ///   *y += 1;
+    ///   drop(y); // explicit drop
+    ///   baz().await;
     /// }
     /// ```
     ///
     /// Use instead:
-    /// ```rust,ignore
-    /// use std::cell::RefCell;
-    ///
+    /// ```rust
+    /// use std::cell::RefCell;
+    /// # async fn baz() {}
     /// async fn foo(x: &RefCell<u32>) {
     ///   {
     ///      let mut y = x.borrow_mut();
     ///      *y += 1;
     ///   }
-    ///   bar.await;
+    ///   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;
     /// }
     /// ```
+    #[clippy::version = "1.49.0"]
     pub AWAIT_HOLDING_REFCELL_REF,
-    pedantic,
-    "Inside an async function, holding a RefCell ref while calling await"
+    suspicious,
+    "inside an async function, holding a `RefCell` ref while calling `await`"
+}
+
+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
+    ///
+    /// ```toml
+    /// await-holding-invalid-types = [
+    ///   # You can specify a type name
+    ///   "CustomLockType",
+    ///   # You can (optionally) specify a reason
+    ///   { path = "OtherCustomLockType", reason = "Relies on a thread local" }
+    /// ]
+    /// ```
+    ///
+    /// ```rust
+    /// # async fn baz() {}
+    /// struct CustomLockType;
+    /// struct OtherCustomLockType;
+    /// async fn foo() {
+    ///   let _x = CustomLockType;
+    ///   let _y = OtherCustomLockType;
+    ///   baz().await; // Lint violation
+    /// }
+    /// ```
+    #[clippy::version = "1.49.0"]
+    pub AWAIT_HOLDING_INVALID_TYPE,
+    suspicious,
+    "holding a type across an await point which is not allowed to be held as per the configuration"
+}
+
+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>,
 }
 
-declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
+impl AwaitHolding {
+    pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
+        Self {
+            conf_invalid_types,
+            def_ids: FxHashMap::default(),
+        }
+    }
+}
 
 impl LateLintPass<'_> for AwaitHolding {
+    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());
+            }
+        }
+    }
+
     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
         use AsyncGeneratorKind::{Block, Closure, Fn};
         if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
             let body_id = BodyId {
                 hir_id: body.value.hir_id,
             };
-            let def_id = cx.tcx.hir().body_owner_def_id(body_id);
-            let typeck_results = cx.tcx.typeck(def_id);
-            check_interior_types(
+            let typeck_results = cx.tcx.typeck_body(body_id);
+            self.check_interior_types(
                 cx,
-                &typeck_results.generator_interior_types.as_ref().skip_binder(),
+                typeck_results.generator_interior_types.as_ref().skip_binder(),
                 body.value.span,
             );
         }
     }
 }
 
-fn check_interior_types(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_note(
-                    cx,
-                    AWAIT_HOLDING_LOCK,
-                    ty_cause.span,
-                    "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await",
-                    ty_cause.scope_span.or(Some(span)),
-                    "these are all the await points this lock is held through",
-                );
-            }
-            if is_refcell_ref(cx, adt.did) {
-                span_lint_and_note(
-                    cx,
-                    AWAIT_HOLDING_REFCELL_REF,
-                    ty_cause.span,
-                    "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await",
-                    ty_cause.scope_span.or(Some(span)),
-                    "these are all the await points this ref is held through",
-                );
+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 \
+                                `MutexGuard` is dropped before calling await",
+                            );
+                            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);
+                }
             }
         }
     }
 }
 
+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,
+        &format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
+        |diag| {
+            if let Some(reason) = reason {
+                diag.note(reason.clone());
+            }
+        },
+    );
+}
+
 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
     match_def_path(cx, def_id, &paths::MUTEX_GUARD)
         || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)