1
1
use clippy_utils:: diagnostics:: span_lint_and_then;
2
2
use clippy_utils:: { match_def_path, paths} ;
3
+ use rustc_data_structures:: fx:: FxHashMap ;
3
4
use rustc_hir:: def_id:: DefId ;
4
- use rustc_hir:: { AsyncGeneratorKind , Body , BodyId , GeneratorKind } ;
5
+ use rustc_hir:: { def :: Res , AsyncGeneratorKind , Body , BodyId , GeneratorKind } ;
5
6
use rustc_lint:: { LateContext , LateLintPass } ;
6
7
use rustc_middle:: ty:: GeneratorInteriorTypeCause ;
7
- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
8
+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
8
9
use rustc_span:: Span ;
9
10
11
+ use crate :: utils:: conf:: DisallowedType ;
12
+
10
13
declare_clippy_lint ! {
11
14
/// ### What it does
12
15
/// Checks for calls to await while holding a non-async-aware MutexGuard.
@@ -127,17 +130,81 @@ declare_clippy_lint! {
127
130
"inside an async function, holding a `RefCell` ref while calling `await`"
128
131
}
129
132
130
- declare_lint_pass ! ( AwaitHolding => [ AWAIT_HOLDING_LOCK , AWAIT_HOLDING_REFCELL_REF ] ) ;
133
+ declare_clippy_lint ! {
134
+ /// ### What it does
135
+ /// Allows users to configure types which should not be held across `await`
136
+ /// suspension points.
137
+ ///
138
+ /// ### Why is this bad?
139
+ /// There are some types which are perfectly "safe" to be used concurrently
140
+ /// from a memory access perspective but will cause bugs at runtime if they
141
+ /// are held in such a way.
142
+ ///
143
+ /// ### Known problems
144
+ ///
145
+ /// ### Example
146
+ ///
147
+ /// The `tracing` library has types which should not be held across `await`
148
+ /// points.
149
+ ///
150
+ /// ```toml
151
+ /// await-holding-invalid-types = [
152
+ /// "tracing::span::Entered",
153
+ /// "tracing::span::EnteredSpan",
154
+ /// ]
155
+ /// ```
156
+ ///
157
+ /// ```rust
158
+ /// # async fn baz() {}
159
+ /// async fn foo() {
160
+ /// let _entered = tracing::info_span!("baz").entered();
161
+ /// baz().await;
162
+ /// }
163
+ /// ```
164
+ #[ clippy:: version = "1.49.0" ]
165
+ pub AWAIT_HOLDING_INVALID_TYPE ,
166
+ suspicious,
167
+ "inside an async function, holding a type across an await point which is not safe to be held across an await point"
168
+ }
169
+
170
+ impl_lint_pass ! ( AwaitHolding => [ AWAIT_HOLDING_LOCK , AWAIT_HOLDING_REFCELL_REF , AWAIT_HOLDING_INVALID_TYPE ] ) ;
171
+
172
+ #[ derive( Debug ) ]
173
+ pub struct AwaitHolding {
174
+ conf_invalid_types : Vec < DisallowedType > ,
175
+ def_ids : FxHashMap < DefId , DisallowedType > ,
176
+ }
177
+
178
+ impl AwaitHolding {
179
+ pub ( crate ) fn new ( conf_invalid_types : Vec < DisallowedType > ) -> Self {
180
+ Self {
181
+ conf_invalid_types,
182
+ def_ids : FxHashMap :: default ( ) ,
183
+ }
184
+ }
185
+ }
131
186
132
187
impl LateLintPass < ' _ > for AwaitHolding {
188
+ fn check_crate ( & mut self , cx : & LateContext < ' _ > ) {
189
+ for conf in & self . conf_invalid_types {
190
+ let path = match conf {
191
+ DisallowedType :: Simple ( path) | DisallowedType :: WithReason { path, .. } => path,
192
+ } ;
193
+ let segs: Vec < _ > = path. split ( "::" ) . collect ( ) ;
194
+ if let Res :: Def ( _, id) = clippy_utils:: def_path_res ( cx, & segs) {
195
+ self . def_ids . insert ( id, conf. clone ( ) ) ;
196
+ }
197
+ }
198
+ }
199
+
133
200
fn check_body ( & mut self , cx : & LateContext < ' _ > , body : & ' _ Body < ' _ > ) {
134
201
use AsyncGeneratorKind :: { Block , Closure , Fn } ;
135
202
if let Some ( GeneratorKind :: Async ( Block | Closure | Fn ) ) = body. generator_kind {
136
203
let body_id = BodyId {
137
204
hir_id : body. value . hir_id ,
138
205
} ;
139
206
let typeck_results = cx. tcx . typeck_body ( body_id) ;
140
- check_interior_types (
207
+ self . check_interior_types (
141
208
cx,
142
209
typeck_results. generator_interior_types . as_ref ( ) . skip_binder ( ) ,
143
210
body. value . span ,
@@ -146,46 +213,68 @@ impl LateLintPass<'_> for AwaitHolding {
146
213
}
147
214
}
148
215
149
- fn check_interior_types ( 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_mutex_guard ( cx, adt. did ( ) ) {
153
- span_lint_and_then (
154
- cx,
155
- AWAIT_HOLDING_LOCK ,
156
- ty_cause. span ,
157
- "this `MutexGuard` is held across an `await` point" ,
158
- |diag| {
159
- diag. help (
160
- "consider using an async-aware `Mutex` type or ensuring the \
216
+ impl AwaitHolding {
217
+ fn check_interior_types ( & self , cx : & LateContext < ' _ > , ty_causes : & [ GeneratorInteriorTypeCause < ' _ > ] , span : Span ) {
218
+ for ty_cause in ty_causes {
219
+ if let rustc_middle:: ty:: Adt ( adt, _) = ty_cause. ty . kind ( ) {
220
+ if is_mutex_guard ( cx, adt. did ( ) ) {
221
+ span_lint_and_then (
222
+ cx,
223
+ AWAIT_HOLDING_LOCK ,
224
+ ty_cause. span ,
225
+ "this `MutexGuard` is held across an `await` point" ,
226
+ |diag| {
227
+ diag. help (
228
+ "consider using an async-aware `Mutex` type or ensuring the \
161
229
`MutexGuard` is dropped before calling await",
162
- ) ;
163
- diag. span_note (
164
- ty_cause. scope_span . unwrap_or ( span) ,
165
- "these are all the `await` points this lock is held through" ,
166
- ) ;
167
- } ,
168
- ) ;
169
- }
170
- if is_refcell_ref ( cx, adt. did ( ) ) {
171
- span_lint_and_then (
172
- cx,
173
- AWAIT_HOLDING_REFCELL_REF ,
174
- ty_cause. span ,
175
- "this `RefCell` reference is held across an `await` point" ,
176
- |diag| {
177
- diag. help ( "ensure the reference is dropped before calling `await`" ) ;
178
- diag. span_note (
179
- ty_cause. scope_span . unwrap_or ( span) ,
180
- "these are all the `await` points this reference is held through" ,
181
- ) ;
182
- } ,
183
- ) ;
230
+ ) ;
231
+ diag. span_note (
232
+ ty_cause. scope_span . unwrap_or ( span) ,
233
+ "these are all the `await` points this lock is held through" ,
234
+ ) ;
235
+ } ,
236
+ ) ;
237
+ } else if is_refcell_ref ( cx, adt. did ( ) ) {
238
+ span_lint_and_then (
239
+ cx,
240
+ AWAIT_HOLDING_REFCELL_REF ,
241
+ ty_cause. span ,
242
+ "this `RefCell` reference is held across an `await` point" ,
243
+ |diag| {
244
+ diag. help ( "ensure the reference is dropped before calling `await`" ) ;
245
+ diag. span_note (
246
+ ty_cause. scope_span . unwrap_or ( span) ,
247
+ "these are all the `await` points this reference is held through" ,
248
+ ) ;
249
+ } ,
250
+ ) ;
251
+ } else if let Some ( disallowed) = self . def_ids . get ( & adt. did ( ) ) {
252
+ emit_invalid_type ( cx, ty_cause. span , disallowed) ;
253
+ }
184
254
}
185
255
}
186
256
}
187
257
}
188
258
259
+ fn emit_invalid_type ( cx : & LateContext < ' _ > , span : Span , disallowed : & DisallowedType ) {
260
+ let ( type_name, reason) = match disallowed {
261
+ DisallowedType :: Simple ( path) => ( path, & None ) ,
262
+ DisallowedType :: WithReason { path, reason } => ( path, reason) ,
263
+ } ;
264
+
265
+ span_lint_and_then (
266
+ cx,
267
+ AWAIT_HOLDING_INVALID_TYPE ,
268
+ span,
269
+ & format ! ( "`{type_name}` may not be held across an `await` point according to config" , ) ,
270
+ |diag| {
271
+ if let Some ( reason) = reason {
272
+ diag. note ( format ! ( "{reason} (according to clippy.toml)" ) ) ;
273
+ }
274
+ } ,
275
+ ) ;
276
+ }
277
+
189
278
fn is_mutex_guard ( cx : & LateContext < ' _ > , def_id : DefId ) -> bool {
190
279
match_def_path ( cx, def_id, & paths:: MUTEX_GUARD )
191
280
|| match_def_path ( cx, def_id, & paths:: RWLOCK_READ_GUARD )
0 commit comments