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