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,83 @@ 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+ /// ```toml
148+ /// await-holding-invalid-types = [
149+ /// # You can specify a type name
150+ /// "CustomLockType",
151+ /// # You can (optionally) specify a reason
152+ /// { path = "OtherCustomLockType", reason = "Relies on a thread local" }
153+ /// ]
154+ /// ```
155+ ///
156+ /// ```rust
157+ /// # async fn baz() {}
158+ /// struct CustomLockType;
159+ /// struct OtherCustomLockType;
160+ /// async fn foo() {
161+ /// let _x = CustomLockType;
162+ /// let _y = OtherCustomLockType;
163+ /// baz().await; // Lint violation
164+ /// }
165+ /// ```
166+ #[ clippy:: version = "1.49.0" ]
167+ pub AWAIT_HOLDING_INVALID_TYPE ,
168+ suspicious,
169+ "holding a type across an await point which is not allowed to be held as per the configuration"
170+ }
171+
172+ impl_lint_pass ! ( AwaitHolding => [ AWAIT_HOLDING_LOCK , AWAIT_HOLDING_REFCELL_REF , AWAIT_HOLDING_INVALID_TYPE ] ) ;
173+
174+ #[ derive( Debug ) ]
175+ pub struct AwaitHolding {
176+ conf_invalid_types : Vec < DisallowedType > ,
177+ def_ids : FxHashMap < DefId , DisallowedType > ,
178+ }
179+
180+ impl AwaitHolding {
181+ pub ( crate ) fn new ( conf_invalid_types : Vec < DisallowedType > ) -> Self {
182+ Self {
183+ conf_invalid_types,
184+ def_ids : FxHashMap :: default ( ) ,
185+ }
186+ }
187+ }
131188
132189impl LateLintPass < ' _ > for AwaitHolding {
190+ fn check_crate ( & mut self , cx : & LateContext < ' _ > ) {
191+ for conf in & self . conf_invalid_types {
192+ let path = match conf {
193+ DisallowedType :: Simple ( path) | DisallowedType :: WithReason { path, .. } => path,
194+ } ;
195+ let segs: Vec < _ > = path. split ( "::" ) . collect ( ) ;
196+ if let Res :: Def ( _, id) = clippy_utils:: def_path_res ( cx, & segs) {
197+ self . def_ids . insert ( id, conf. clone ( ) ) ;
198+ }
199+ }
200+ }
201+
133202 fn check_body ( & mut self , cx : & LateContext < ' _ > , body : & ' _ Body < ' _ > ) {
134203 use AsyncGeneratorKind :: { Block , Closure , Fn } ;
135204 if let Some ( GeneratorKind :: Async ( Block | Closure | Fn ) ) = body. generator_kind {
136205 let body_id = BodyId {
137206 hir_id : body. value . hir_id ,
138207 } ;
139208 let typeck_results = cx. tcx . typeck_body ( body_id) ;
140- check_interior_types (
209+ self . check_interior_types (
141210 cx,
142211 typeck_results. generator_interior_types . as_ref ( ) . skip_binder ( ) ,
143212 body. value . span ,
@@ -146,46 +215,68 @@ impl LateLintPass<'_> for AwaitHolding {
146215 }
147216}
148217
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 \
218+ impl AwaitHolding {
219+ fn check_interior_types ( & self , cx : & LateContext < ' _ > , ty_causes : & [ GeneratorInteriorTypeCause < ' _ > ] , span : Span ) {
220+ for ty_cause in ty_causes {
221+ if let rustc_middle:: ty:: Adt ( adt, _) = ty_cause. ty . kind ( ) {
222+ if is_mutex_guard ( cx, adt. did ( ) ) {
223+ span_lint_and_then (
224+ cx,
225+ AWAIT_HOLDING_LOCK ,
226+ ty_cause. span ,
227+ "this `MutexGuard` is held across an `await` point" ,
228+ |diag| {
229+ diag. help (
230+ "consider using an async-aware `Mutex` type or ensuring the \
161231 `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- ) ;
232+ ) ;
233+ diag. span_note (
234+ ty_cause. scope_span . unwrap_or ( span) ,
235+ "these are all the `await` points this lock is held through" ,
236+ ) ;
237+ } ,
238+ ) ;
239+ } else if is_refcell_ref ( cx, adt. did ( ) ) {
240+ span_lint_and_then (
241+ cx,
242+ AWAIT_HOLDING_REFCELL_REF ,
243+ ty_cause. span ,
244+ "this `RefCell` reference is held across an `await` point" ,
245+ |diag| {
246+ diag. help ( "ensure the reference is dropped before calling `await`" ) ;
247+ diag. span_note (
248+ ty_cause. scope_span . unwrap_or ( span) ,
249+ "these are all the `await` points this reference is held through" ,
250+ ) ;
251+ } ,
252+ ) ;
253+ } else if let Some ( disallowed) = self . def_ids . get ( & adt. did ( ) ) {
254+ emit_invalid_type ( cx, ty_cause. span , disallowed) ;
255+ }
184256 }
185257 }
186258 }
187259}
188260
261+ fn emit_invalid_type ( cx : & LateContext < ' _ > , span : Span , disallowed : & DisallowedType ) {
262+ let ( type_name, reason) = match disallowed {
263+ DisallowedType :: Simple ( path) => ( path, & None ) ,
264+ DisallowedType :: WithReason { path, reason } => ( path, reason) ,
265+ } ;
266+
267+ span_lint_and_then (
268+ cx,
269+ AWAIT_HOLDING_INVALID_TYPE ,
270+ span,
271+ & format ! ( "`{type_name}` may not be held across an `await` point per `clippy.toml`" , ) ,
272+ |diag| {
273+ if let Some ( reason) = reason {
274+ diag. note ( reason. clone ( ) ) ;
275+ }
276+ } ,
277+ ) ;
278+ }
279+
189280fn is_mutex_guard ( cx : & LateContext < ' _ > , def_id : DefId ) -> bool {
190281 match_def_path ( cx, def_id, & paths:: MUTEX_GUARD )
191282 || match_def_path ( cx, def_id, & paths:: RWLOCK_READ_GUARD )
0 commit comments