11use clippy_utils:: diagnostics:: span_lint_and_then;
22use clippy_utils:: { match_def_path, paths} ;
3+ use rustc_data_structures:: fx:: FxHashMap ;
34use rustc_hir:: def_id:: DefId ;
4- use rustc_hir:: { AsyncGeneratorKind , Body , BodyId , GeneratorKind } ;
5+ use rustc_hir:: { def :: Res , AsyncGeneratorKind , Body , BodyId , GeneratorKind } ;
56use rustc_lint:: { LateContext , LateLintPass } ;
67use rustc_middle:: ty:: GeneratorInteriorTypeCause ;
7- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
8+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
89use rustc_span:: Span ;
910
11+ use crate :: utils:: conf:: DisallowedType ;
12+
1013declare_clippy_lint ! {
1114 /// ### What it does
1215 /// Checks for calls to await while holding a non-async-aware MutexGuard.
@@ -127,17 +130,81 @@ declare_clippy_lint! {
127130 "inside an async function, holding a `RefCell` ref while calling `await`"
128131}
129132
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+ }
131186
132187impl 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+
133200 fn check_body ( & mut self , cx : & LateContext < ' _ > , body : & ' _ Body < ' _ > ) {
134201 use AsyncGeneratorKind :: { Block , Closure , Fn } ;
135202 if let Some ( GeneratorKind :: Async ( Block | Closure | Fn ) ) = body. generator_kind {
136203 let body_id = BodyId {
137204 hir_id : body. value . hir_id ,
138205 } ;
139206 let typeck_results = cx. tcx . typeck_body ( body_id) ;
140- check_interior_types (
207+ self . check_interior_types (
141208 cx,
142209 typeck_results. generator_interior_types . as_ref ( ) . skip_binder ( ) ,
143210 body. value . span ,
@@ -146,46 +213,68 @@ impl LateLintPass<'_> for AwaitHolding {
146213 }
147214}
148215
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 \
161229 `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+ }
184254 }
185255 }
186256 }
187257}
188258
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+
189278fn is_mutex_guard ( cx : & LateContext < ' _ > , def_id : DefId ) -> bool {
190279 match_def_path ( cx, def_id, & paths:: MUTEX_GUARD )
191280 || match_def_path ( cx, def_id, & paths:: RWLOCK_READ_GUARD )
0 commit comments