]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/await_holding_invalid.rs
Auto merge of #8625 - Jarcho:rename_lint, r=xFrednet
[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_data_structures::fx::FxHashMap;
4 use rustc_hir::def_id::DefId;
5 use rustc_hir::{def::Res, AsyncGeneratorKind, Body, BodyId, GeneratorKind};
6 use rustc_lint::{LateContext, LateLintPass};
7 use rustc_middle::ty::GeneratorInteriorTypeCause;
8 use rustc_session::{declare_tool_lint, impl_lint_pass};
9 use rustc_span::Span;
10
11 use crate::utils::conf::DisallowedType;
12
13 declare_clippy_lint! {
14     /// ### What it does
15     /// Checks for calls to await while holding a non-async-aware MutexGuard.
16     ///
17     /// ### Why is this bad?
18     /// The Mutex types found in std::sync and parking_lot
19     /// are not designed to operate in an async context across await points.
20     ///
21     /// There are two potential solutions. One is to use an async-aware Mutex
22     /// type. Many asynchronous foundation crates provide such a Mutex type. The
23     /// other solution is to ensure the mutex is unlocked before calling await,
24     /// either by introducing a scope or an explicit call to Drop::drop.
25     ///
26     /// ### Known problems
27     /// Will report false positive for explicitly dropped guards
28     /// ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)). A workaround for this is
29     /// to wrap the `.lock()` call in a block instead of explicitly dropping the guard.
30     ///
31     /// ### Example
32     /// ```rust
33     /// # use std::sync::Mutex;
34     /// # async fn baz() {}
35     /// async fn foo(x: &Mutex<u32>) {
36     ///   let mut guard = x.lock().unwrap();
37     ///   *guard += 1;
38     ///   baz().await;
39     /// }
40     ///
41     /// async fn bar(x: &Mutex<u32>) {
42     ///   let mut guard = x.lock().unwrap();
43     ///   *guard += 1;
44     ///   drop(guard); // explicit drop
45     ///   baz().await;
46     /// }
47     /// ```
48     ///
49     /// Use instead:
50     /// ```rust
51     /// # use std::sync::Mutex;
52     /// # async fn baz() {}
53     /// async fn foo(x: &Mutex<u32>) {
54     ///   {
55     ///     let mut guard = x.lock().unwrap();
56     ///     *guard += 1;
57     ///   }
58     ///   baz().await;
59     /// }
60     ///
61     /// async fn bar(x: &Mutex<u32>) {
62     ///   {
63     ///     let mut guard = x.lock().unwrap();
64     ///     *guard += 1;
65     ///   } // guard dropped here at end of scope
66     ///   baz().await;
67     /// }
68     /// ```
69     #[clippy::version = "1.45.0"]
70     pub AWAIT_HOLDING_LOCK,
71     suspicious,
72     "inside an async function, holding a `MutexGuard` while calling `await`"
73 }
74
75 declare_clippy_lint! {
76     /// ### What it does
77     /// Checks for calls to await while holding a `RefCell` `Ref` or `RefMut`.
78     ///
79     /// ### Why is this bad?
80     /// `RefCell` refs only check for exclusive mutable access
81     /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
82     /// risks panics from a mutable ref shared while other refs are outstanding.
83     ///
84     /// ### Known problems
85     /// Will report false positive for explicitly dropped refs
86     /// ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)). A workaround for this is
87     /// to wrap the `.borrow[_mut]()` call in a block instead of explicitly dropping the ref.
88     ///
89     /// ### Example
90     /// ```rust
91     /// # use std::cell::RefCell;
92     /// # async fn baz() {}
93     /// async fn foo(x: &RefCell<u32>) {
94     ///   let mut y = x.borrow_mut();
95     ///   *y += 1;
96     ///   baz().await;
97     /// }
98     ///
99     /// async fn bar(x: &RefCell<u32>) {
100     ///   let mut y = x.borrow_mut();
101     ///   *y += 1;
102     ///   drop(y); // explicit drop
103     ///   baz().await;
104     /// }
105     /// ```
106     ///
107     /// Use instead:
108     /// ```rust
109     /// # use std::cell::RefCell;
110     /// # async fn baz() {}
111     /// async fn foo(x: &RefCell<u32>) {
112     ///   {
113     ///      let mut y = x.borrow_mut();
114     ///      *y += 1;
115     ///   }
116     ///   baz().await;
117     /// }
118     ///
119     /// async fn bar(x: &RefCell<u32>) {
120     ///   {
121     ///     let mut y = x.borrow_mut();
122     ///     *y += 1;
123     ///   } // y dropped here at end of scope
124     ///   baz().await;
125     /// }
126     /// ```
127     #[clippy::version = "1.49.0"]
128     pub AWAIT_HOLDING_REFCELL_REF,
129     suspicious,
130     "inside an async function, holding a `RefCell` ref while calling `await`"
131 }
132
133 declare_clippy_lint! {
134     /// ### What it does
135     /// Allows users to configure types which should not be held across `await`
136     /// suspension points.
137     ///
138     /// ### Why is this bad?
139     /// There are some types which are perfectly "safe" to be used concurrently
140     /// from a memory access perspective but will cause bugs at runtime if they
141     /// are held in such a way.
142     ///
143     /// ### Known problems
144     ///
145     /// ### Example
146     ///
147     /// ```toml
148     /// await-holding-invalid-types = [
149     ///   # You can specify a type name
150     ///   "CustomLockType",
151     ///   # You can (optionally) specify a reason
152     ///   { path = "OtherCustomLockType", reason = "Relies on a thread local" }
153     /// ]
154     /// ```
155     ///
156     /// ```rust
157     /// # async fn baz() {}
158     /// struct CustomLockType;
159     /// struct OtherCustomLockType;
160     /// async fn foo() {
161     ///   let _x = CustomLockType;
162     ///   let _y = OtherCustomLockType;
163     ///   baz().await; // Lint violation
164     /// }
165     /// ```
166     #[clippy::version = "1.49.0"]
167     pub AWAIT_HOLDING_INVALID_TYPE,
168     suspicious,
169     "holding a type across an await point which is not allowed to be held as per the configuration"
170 }
171
172 impl_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF, AWAIT_HOLDING_INVALID_TYPE]);
173
174 #[derive(Debug)]
175 pub struct AwaitHolding {
176     conf_invalid_types: Vec<DisallowedType>,
177     def_ids: FxHashMap<DefId, DisallowedType>,
178 }
179
180 impl AwaitHolding {
181     pub(crate) fn new(conf_invalid_types: Vec<DisallowedType>) -> Self {
182         Self {
183             conf_invalid_types,
184             def_ids: FxHashMap::default(),
185         }
186     }
187 }
188
189 impl LateLintPass<'_> for AwaitHolding {
190     fn check_crate(&mut self, cx: &LateContext<'_>) {
191         for conf in &self.conf_invalid_types {
192             let path = match conf {
193                 DisallowedType::Simple(path) | DisallowedType::WithReason { path, .. } => path,
194             };
195             let segs: Vec<_> = path.split("::").collect();
196             if let Res::Def(_, id) = clippy_utils::def_path_res(cx, &segs) {
197                 self.def_ids.insert(id, conf.clone());
198             }
199         }
200     }
201
202     fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
203         use AsyncGeneratorKind::{Block, Closure, Fn};
204         if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
205             let body_id = BodyId {
206                 hir_id: body.value.hir_id,
207             };
208             let typeck_results = cx.tcx.typeck_body(body_id);
209             self.check_interior_types(
210                 cx,
211                 typeck_results.generator_interior_types.as_ref().skip_binder(),
212                 body.value.span,
213             );
214         }
215     }
216 }
217
218 impl AwaitHolding {
219     fn check_interior_types(&self, cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
220         for ty_cause in ty_causes {
221             if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
222                 if is_mutex_guard(cx, adt.did()) {
223                     span_lint_and_then(
224                         cx,
225                         AWAIT_HOLDING_LOCK,
226                         ty_cause.span,
227                         "this `MutexGuard` is held across an `await` point",
228                         |diag| {
229                             diag.help(
230                                 "consider using an async-aware `Mutex` type or ensuring the \
231                                 `MutexGuard` is dropped before calling await",
232                             );
233                             diag.span_note(
234                                 ty_cause.scope_span.unwrap_or(span),
235                                 "these are all the `await` points this lock is held through",
236                             );
237                         },
238                     );
239                 } else if is_refcell_ref(cx, adt.did()) {
240                     span_lint_and_then(
241                         cx,
242                         AWAIT_HOLDING_REFCELL_REF,
243                         ty_cause.span,
244                         "this `RefCell` reference is held across an `await` point",
245                         |diag| {
246                             diag.help("ensure the reference is dropped before calling `await`");
247                             diag.span_note(
248                                 ty_cause.scope_span.unwrap_or(span),
249                                 "these are all the `await` points this reference is held through",
250                             );
251                         },
252                     );
253                 } else if let Some(disallowed) = self.def_ids.get(&adt.did()) {
254                     emit_invalid_type(cx, ty_cause.span, disallowed);
255                 }
256             }
257         }
258     }
259 }
260
261 fn emit_invalid_type(cx: &LateContext<'_>, span: Span, disallowed: &DisallowedType) {
262     let (type_name, reason) = match disallowed {
263         DisallowedType::Simple(path) => (path, &None),
264         DisallowedType::WithReason { path, reason } => (path, reason),
265     };
266
267     span_lint_and_then(
268         cx,
269         AWAIT_HOLDING_INVALID_TYPE,
270         span,
271         &format!("`{type_name}` may not be held across an `await` point per `clippy.toml`",),
272         |diag| {
273             if let Some(reason) = reason {
274                 diag.note(reason.clone());
275             }
276         },
277     );
278 }
279
280 fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
281     match_def_path(cx, def_id, &paths::MUTEX_GUARD)
282         || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
283         || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
284         || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
285         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
286         || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
287 }
288
289 fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
290     match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
291 }