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