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