]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/await_holding_invalid.rs
Move await_holding_* lints to suspicious and improve doc
[rust.git] / clippy_lints / src / await_holding_invalid.rs
1 use clippy_utils::diagnostics::span_lint_and_then;
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
12     /// Checks for calls to await while holding a non-async-aware MutexGuard.
13     ///
14     /// ### Why is this bad?
15     /// The Mutex types found in std::sync and parking_lot
16     /// are not designed to operate in an async context across await points.
17     ///
18     /// There are two potential solutions. One is to use an async-aware Mutex
19     /// type. Many asynchronous foundation crates provide such a Mutex type. The
20     /// other solution is to ensure the mutex is unlocked before calling await,
21     /// either by introducing a scope or an explicit call to Drop::drop.
22     ///
23     /// ### Known problems
24     /// Will report false positive for explicitly dropped guards
25     /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
26     /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
27     ///
28     /// ### Example
29     /// ```rust
30     /// # use std::sync::Mutex;
31     /// # async fn baz() {}
32     /// async fn foo(x: &Mutex<u32>) {
33     ///   let mut guard = x.lock().unwrap();
34     ///   *guard += 1;
35     ///   baz().await;
36     /// }
37     ///
38     /// async fn bar(x: &Mutex<u32>) {
39     ///   let mut guard = x.lock().unwrap();
40     ///   *guard += 1;
41     ///   drop(guard); // explicit drop
42     ///   baz().await;
43     /// }
44     /// ```
45     ///
46     /// Use instead:
47     /// ```rust
48     /// # use std::sync::Mutex;
49     /// # async fn baz() {}
50     /// async fn foo(x: &Mutex<u32>) {
51     ///   {
52     ///     let mut guard = x.lock().unwrap();
53     ///     *guard += 1;
54     ///   }
55     ///   baz().await;
56     /// }
57     ///
58     /// async fn bar(x: &Mutex<u32>) {
59     ///   {
60     ///     let mut guard = x.lock().unwrap();
61     ///     *guard += 1;
62     ///   } // guard dropped here at end of scope
63     ///   baz().await;
64     /// }
65     /// ```
66     #[clippy::version = "1.45.0"]
67     pub AWAIT_HOLDING_LOCK,
68     suspicious,
69     "inside an async function, holding a `MutexGuard` while calling `await`"
70 }
71
72 declare_clippy_lint! {
73     /// ### What it does
74     /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
75     ///
76     /// ### Why is this bad?
77     /// `RefCell` refs only check for exclusive mutable access
78     /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
79     /// risks panics from a mutable ref shared while other refs are outstanding.
80     ///
81     /// ### Known problems
82     /// Will report false positive for explicitly dropped refs
83     /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
84     /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
85     ///
86     /// ### Example
87     /// ```rust
88     /// # use std::cell::RefCell;
89     /// # async fn baz() {}
90     /// async fn foo(x: &RefCell<u32>) {
91     ///   let mut y = x.borrow_mut();
92     ///   *y += 1;
93     ///   baz().await;
94     /// }
95     ///
96     /// async fn bar(x: &RefCell<u32>) {
97     ///   let mut y = x.borrow_mut();
98     ///   *y += 1;
99     ///   drop(y); // explicit drop
100     ///   baz().await;
101     /// }
102     /// ```
103     ///
104     /// Use instead:
105     /// ```rust
106     /// # use std::cell::RefCell;
107     /// # async fn baz() {}
108     /// async fn foo(x: &RefCell<u32>) {
109     ///   {
110     ///      let mut y = x.borrow_mut();
111     ///      *y += 1;
112     ///   }
113     ///   baz().await;
114     /// }
115     ///
116     /// async fn bar(x: &RefCell<u32>) {
117     ///   {
118     ///     let mut y = x.borrow_mut();
119     ///     *y += 1;
120     ///   } // y dropped here at end of scope
121     ///   baz().await;
122     /// }
123     /// ```
124     #[clippy::version = "1.49.0"]
125     pub AWAIT_HOLDING_REFCELL_REF,
126     suspicious,
127     "inside an async function, holding a `RefCell` ref while calling `await`"
128 }
129
130 declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);
131
132 impl LateLintPass<'_> for AwaitHolding {
133     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
134         use AsyncGeneratorKind::{Block, Closure, Fn};
135         if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
136             let body_id = BodyId {
137                 hir_id: body.value.hir_id,
138             };
139             let typeck_results = cx.tcx.typeck_body(body_id);
140             check_interior_types(
141                 cx,
142                 typeck_results.generator_interior_types.as_ref().skip_binder(),
143                 body.value.span,
144             );
145         }
146     }
147 }
148
149 fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
150     for ty_cause in ty_causes {
151         if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
152             if is_mutex_guard(cx, adt.did) {
153                 span_lint_and_then(
154                     cx,
155                     AWAIT_HOLDING_LOCK,
156                     ty_cause.span,
157                     "this `MutexGuard` is held across an `await` point",
158                     |diag| {
159                         diag.help(
160                             "consider using an async-aware `Mutex` type or ensuring the \
161                                 `MutexGuard` is dropped before calling await",
162                         );
163                         diag.span_note(
164                             ty_cause.scope_span.unwrap_or(span),
165                             "these are all the `await` points this lock is held through",
166                         );
167                     },
168                 );
169             }
170             if is_refcell_ref(cx, adt.did) {
171                 span_lint_and_then(
172                     cx,
173                     AWAIT_HOLDING_REFCELL_REF,
174                     ty_cause.span,
175                     "this `RefCell` reference is held across an `await` point",
176                     |diag| {
177                         diag.help("ensure the reference is dropped before calling `await`");
178                         diag.span_note(
179                             ty_cause.scope_span.unwrap_or(span),
180                             "these are all the `await` points this reference is held through",
181                         );
182                     },
183                 );
184             }
185         }
186     }
187 }
188
189 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
190     match_def_path(cx, def_id, &paths::MUTEX_GUARD)
191         || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
192         || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
193         || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
194         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
195         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
196 }
197
198 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
199     match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
200 }