]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::span_lint_and_note; |
2 | use clippy_utils::{match_def_path, paths}; | |
f20569fa XL |
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, | |
cdc7bbd5 | 104 | typeck_results.generator_interior_types.as_ref().skip_binder(), |
f20569fa XL |
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 | } |