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