2020-04-10 04:50:23 +00:00
use crate ::utils ::{ match_def_path , paths , span_lint_and_note } ;
use rustc_hir ::def_id ::DefId ;
2020-04-17 06:21:49 +00:00
use rustc_hir ::{ AsyncGeneratorKind , Body , BodyId , GeneratorKind } ;
2020-04-08 04:20:37 +00:00
use rustc_lint ::{ LateContext , LateLintPass } ;
2020-04-17 06:21:49 +00:00
use rustc_middle ::ty ::GeneratorInteriorTypeCause ;
2020-04-08 04:20:37 +00:00
use rustc_session ::{ declare_lint_pass , declare_tool_lint } ;
2020-04-10 04:50:23 +00:00
use rustc_span ::Span ;
2020-04-08 04:20:37 +00:00
declare_clippy_lint! {
2020-04-17 06:21:49 +00:00
/// **What it does:** Checks for calls to await while holding a
/// non-async-aware MutexGuard.
2020-04-08 04:20:37 +00:00
///
2020-04-17 06:21:49 +00:00
/// **Why is this bad?** The Mutex types found in syd::sync and parking_lot
/// are not designed to operator in an async context across await points.
2020-04-08 04:20:37 +00:00
///
2020-04-17 06:21:49 +00:00
/// There are two potential solutions. One is to use an asynx-aware Mutex
/// type. Many asynchronous foundation crates provide such a Mutex type. The
/// other solution is to ensure the mutex is unlocked before calling await,
/// either by introducing a scope or an explicit call to Drop::drop.
///
/// **Known problems:** None.
2020-04-08 04:20:37 +00:00
///
/// **Example:**
///
2020-04-10 05:12:34 +00:00
/// ```rust,ignore
2020-04-08 04:20:37 +00:00
/// use std::sync::Mutex;
///
/// async fn foo(x: &Mutex<u32>) {
/// let guard = x.lock().unwrap();
/// *guard += 1;
/// bar.await;
/// }
/// ```
2020-04-17 06:21:49 +00:00
///
2020-04-08 04:20:37 +00:00
/// Use instead:
2020-04-10 05:12:34 +00:00
/// ```rust,ignore
2020-04-08 04:20:37 +00:00
/// use std::sync::Mutex;
///
/// async fn foo(x: &Mutex<u32>) {
/// {
/// let guard = x.lock().unwrap();
/// *guard += 1;
/// }
/// bar.await;
/// }
/// ```
pub AWAIT_HOLDING_LOCK ,
pedantic ,
" Inside an async function, holding a MutexGuard while calling await "
}
declare_lint_pass! ( AwaitHoldingLock = > [ AWAIT_HOLDING_LOCK ] ) ;
impl LateLintPass < '_ , '_ > for AwaitHoldingLock {
2020-04-17 06:21:49 +00:00
fn check_body ( & mut self , cx : & LateContext < '_ , '_ > , body : & '_ Body < '_ > ) {
use AsyncGeneratorKind ::{ Block , Closure , Fn } ;
match body . generator_kind {
Some ( GeneratorKind ::Async ( Block ) )
| Some ( GeneratorKind ::Async ( Closure ) )
| Some ( GeneratorKind ::Async ( Fn ) ) = > {
let body_id = BodyId {
hir_id : body . value . hir_id ,
} ;
let def_id = cx . tcx . hir ( ) . body_owner_def_id ( body_id ) ;
let tables = cx . tcx . typeck_tables_of ( def_id ) ;
check_interior_types ( cx , & tables . generator_interior_types , body . value . span ) ;
} ,
_ = > { } ,
2020-04-08 04:20:37 +00:00
}
2020-04-17 06:21:49 +00:00
}
}
2020-04-08 04:20:37 +00:00
2020-04-17 06:21:49 +00:00
fn check_interior_types ( cx : & LateContext < '_ , '_ > , ty_causes : & [ GeneratorInteriorTypeCause < '_ > ] , span : Span ) {
for ty_cause in ty_causes {
if let rustc_middle ::ty ::Adt ( adt , _ ) = ty_cause . ty . kind {
if is_mutex_guard ( cx , adt . did ) {
span_lint_and_note (
cx ,
AWAIT_HOLDING_LOCK ,
ty_cause . span ,
" this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await. " ,
ty_cause . scope_span . unwrap_or ( span ) ,
" these are all the await points this lock is held through " ,
) ;
2020-04-08 04:20:37 +00:00
}
}
}
}
2020-04-10 04:50:23 +00:00
fn is_mutex_guard ( cx : & LateContext < '_ , '_ > , def_id : DefId ) -> bool {
match_def_path ( cx , def_id , & paths ::MUTEX_GUARD )
| | match_def_path ( cx , def_id , & paths ::RWLOCK_READ_GUARD )
| | match_def_path ( cx , def_id , & paths ::RWLOCK_WRITE_GUARD )
| | match_def_path ( cx , def_id , & paths ::PARKING_LOT_MUTEX_GUARD )
| | match_def_path ( cx , def_id , & paths ::PARKING_LOT_RWLOCK_READ_GUARD )
| | match_def_path ( cx , def_id , & paths ::PARKING_LOT_RWLOCK_WRITE_GUARD )
2020-04-08 04:20:37 +00:00
}