]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/await_holding_invalid.rs
Merge commit '0e87918536b9833bbc6c683d1f9d51ee2bf03ef1' into clippyup
[rust.git] / clippy_lints / src / await_holding_invalid.rs
1 use clippy_utils::diagnostics::span_lint_and_note;
2 use clippy_utils::{match_def_path, paths};
3 use rustc_hir::def_id::DefId;
4 use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_middle::ty::GeneratorInteriorTypeCause;
7 use rustc_session::{declare_lint_pass, declare_tool_lint};
8 use rustc_span::Span;
9
10 declare_clippy_lint! {
11     /// **What it does:** Checks for calls to await while holding a
12     /// non-async-aware MutexGuard.
13     ///
14     /// **Why is this bad?** The Mutex types found in std::sync and parking_lot
15     /// are not designed to operate in an async context across await points.
16     ///
17     /// There are two potential solutions. One is to use an asynx-aware Mutex
18     /// type. Many asynchronous foundation crates provide such a Mutex type. The
19     /// other solution is to ensure the mutex is unlocked before calling await,
20     /// either by introducing a scope or an explicit call to Drop::drop.
21     ///
22     /// **Known problems:** Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)).
23     ///
24     /// **Example:**
25     ///
26     /// ```rust,ignore
27     /// use std::sync::Mutex;
28     ///
29     /// async fn foo(x: &Mutex<u32>) {
30     ///   let guard = x.lock().unwrap();
31     ///   *guard += 1;
32     ///   bar.await;
33     /// }
34     /// ```
35     ///
36     /// Use instead:
37     /// ```rust,ignore
38     /// use std::sync::Mutex;
39     ///
40     /// async fn foo(x: &Mutex<u32>) {
41     ///   {
42     ///     let guard = x.lock().unwrap();
43     ///     *guard += 1;
44     ///   }
45     ///   bar.await;
46     /// }
47     /// ```
48     pub AWAIT_HOLDING_LOCK,
49     pedantic,
50     "Inside an async function, holding a MutexGuard while calling await"
51 }
52
53 declare_clippy_lint! {
54     /// **What it does:** Checks for calls to await while holding a
55     /// `RefCell` `Ref` or `RefMut`.
56     ///
57     /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access
58     /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
59     /// risks panics from a mutable ref shared while other refs are outstanding.
60     ///
61     /// **Known problems:** Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)).
62     ///
63     /// **Example:**
64     ///
65     /// ```rust,ignore
66     /// use std::cell::RefCell;
67     ///
68     /// async fn foo(x: &RefCell<u32>) {
69     ///   let mut y = x.borrow_mut();
70     ///   *y += 1;
71     ///   bar.await;
72     /// }
73     /// ```
74     ///
75     /// Use instead:
76     /// ```rust,ignore
77     /// use std::cell::RefCell;
78     ///
79     /// async fn foo(x: &RefCell<u32>) {
80     ///   {
81     ///      let mut y = x.borrow_mut();
82     ///      *y += 1;
83     ///   }
84     ///   bar.await;
85     /// }
86     /// ```
87     pub AWAIT_HOLDING_REFCELL_REF,
88     pedantic,
89     "Inside an async function, holding a RefCell ref while calling await"
90 }
91
92 declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
93
94 impl LateLintPass<'_> for AwaitHolding {
95     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
96         use AsyncGeneratorKind::{Block, Closure, Fn};
97         if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
98             let body_id = BodyId {
99                 hir_id: body.value.hir_id,
100             };
101             let typeck_results = cx.tcx.typeck_body(body_id);
102             check_interior_types(
103                 cx,
104                 &typeck_results.generator_interior_types.as_ref().skip_binder(),
105                 body.value.span,
106             );
107         }
108     }
109 }
110
111 fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
112     for ty_cause in ty_causes {
113         if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
114             if is_mutex_guard(cx, adt.did) {
115                 span_lint_and_note(
116                     cx,
117                     AWAIT_HOLDING_LOCK,
118                     ty_cause.span,
119                     "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await",
120                     ty_cause.scope_span.or(Some(span)),
121                     "these are all the await points this lock is held through",
122                 );
123             }
124             if is_refcell_ref(cx, adt.did) {
125                 span_lint_and_note(
126                     cx,
127                     AWAIT_HOLDING_REFCELL_REF,
128                     ty_cause.span,
129                     "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await",
130                     ty_cause.scope_span.or(Some(span)),
131                     "these are all the await points this ref is held through",
132                 );
133             }
134         }
135     }
136 }
137
138 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
139     match_def_path(cx, def_id, &paths::MUTEX_GUARD)
140         || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
141         || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
142         || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
143         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
144         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
145 }
146
147 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
148     match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
149 }