2015-12-04 10:12:53 +00:00
use rustc ::lint ::* ;
2016-02-01 11:35:01 +00:00
use rustc ::front ::map ::Node ::{ NodeExpr , NodeStmt } ;
2015-12-04 10:12:53 +00:00
use rustc_front ::hir ::* ;
use rustc_front ::intravisit as visit ;
use rustc ::middle ::ty ;
use rustc ::middle ::ty ::adjustment ::AutoAdjustment ;
use rustc ::middle ::expr_use_visitor ::* ;
use rustc ::middle ::infer ;
use rustc ::middle ::mem_categorization ::{ cmt , Categorization } ;
use rustc ::util ::nodemap ::NodeSet ;
use syntax ::ast ::NodeId ;
use syntax ::codemap ::Span ;
use utils ::span_lint ;
pub struct EscapePass ;
2016-01-01 16:48:19 +00:00
/// **What it does:** This lint checks for usage of `Box<T>` where an unboxed `T` would work fine. It is `Warn` by default.
2015-12-13 03:38:58 +00:00
///
2016-01-01 16:49:01 +00:00
/// **Why is this bad?** This is an unnecessary allocation, and bad for performance. It is only necessary to allocate if you wish to move the box into something.
2015-12-13 03:38:58 +00:00
///
2016-01-01 16:49:01 +00:00
/// **Known problems:** None
2015-12-13 03:38:58 +00:00
///
/// **Example:**
///
/// ```rust
/// fn main() {
/// let x = Box::new(1);
/// foo(*x);
/// println!("{}", *x);
/// }
2015-12-14 20:17:11 +00:00
/// ```
2015-12-04 10:12:53 +00:00
declare_lint! ( pub BOXED_LOCAL , Warn , " using Box<T> where unnecessary " ) ;
2016-02-01 19:37:07 +00:00
fn is_non_trait_box ( ty : ty ::Ty ) -> bool {
2015-12-28 14:12:57 +00:00
match ty . sty {
2016-02-01 19:37:07 +00:00
ty ::TyBox ( ref inner ) = > ! inner . is_trait ( ) ,
2015-12-28 14:12:57 +00:00
_ = > false
}
}
2015-12-04 10:12:53 +00:00
struct EscapeDelegate < ' a , ' tcx : ' a > {
cx : & ' a LateContext < ' a , ' tcx > ,
set : NodeSet ,
}
impl LintPass for EscapePass {
fn get_lints ( & self ) -> LintArray {
lint_array! ( BOXED_LOCAL )
}
}
impl LateLintPass for EscapePass {
2016-01-04 04:26:12 +00:00
fn check_fn ( & mut self , cx : & LateContext , _ : visit ::FnKind , decl : & FnDecl , body : & Block , _ : Span , id : NodeId ) {
2015-12-04 10:12:53 +00:00
let param_env = ty ::ParameterEnvironment ::for_item ( cx . tcx , id ) ;
2016-01-18 12:27:42 +00:00
let infcx = infer ::new_infer_ctxt ( cx . tcx , & cx . tcx . tables , Some ( param_env ) ) ;
2015-12-04 10:12:53 +00:00
let mut v = EscapeDelegate {
cx : cx ,
set : NodeSet ( ) ,
} ;
{
let mut vis = ExprUseVisitor ::new ( & mut v , & infcx ) ;
vis . walk_fn ( decl , body ) ;
}
for node in v . set {
span_lint ( cx ,
BOXED_LOCAL ,
cx . tcx . map . span ( node ) ,
" local variable doesn't need to be boxed here " ) ;
}
}
}
impl < ' a , ' tcx : ' a > Delegate < ' tcx > for EscapeDelegate < ' a , ' tcx > {
2016-01-04 04:26:12 +00:00
fn consume ( & mut self , _ : NodeId , _ : Span , cmt : cmt < ' tcx > , mode : ConsumeMode ) {
2015-12-04 10:12:53 +00:00
if let Categorization ::Local ( lid ) = cmt . cat {
if self . set . contains ( & lid ) {
if let Move ( DirectRefMove ) = mode {
// moved out or in. clearly can't be localized
self . set . remove ( & lid ) ;
}
}
}
}
fn matched_pat ( & mut self , _ : & Pat , _ : cmt < ' tcx > , _ : MatchMode ) { }
fn consume_pat ( & mut self , consume_pat : & Pat , cmt : cmt < ' tcx > , _ : ConsumeMode ) {
2016-02-01 11:35:01 +00:00
let map = & self . cx . tcx . map ;
if map . is_argument ( consume_pat . id ) {
// Skip closure arguments
if let Some ( NodeExpr ( .. ) ) = map . find ( map . get_parent_node ( consume_pat . id ) ) {
return ;
}
2016-02-01 19:37:07 +00:00
if is_non_trait_box ( cmt . ty ) {
2015-12-28 14:12:57 +00:00
self . set . insert ( consume_pat . id ) ;
}
return ;
}
2015-12-04 10:12:53 +00:00
if let Categorization ::Rvalue ( .. ) = cmt . cat {
2016-02-01 11:35:01 +00:00
if let Some ( NodeStmt ( st ) ) = map . find ( map . get_parent_node ( cmt . id ) ) {
2015-12-04 10:12:53 +00:00
if let StmtDecl ( ref decl , _ ) = st . node {
if let DeclLocal ( ref loc ) = decl . node {
if let Some ( ref ex ) = loc . init {
if let ExprBox ( .. ) = ex . node {
2016-02-01 19:37:07 +00:00
if is_non_trait_box ( cmt . ty ) {
2015-12-04 10:12:53 +00:00
// let x = box (...)
self . set . insert ( consume_pat . id ) ;
}
// TODO Box::new
// TODO vec![]
// TODO "foo".to_owned() and friends
}
}
}
}
}
}
if let Categorization ::Local ( lid ) = cmt . cat {
if self . set . contains ( & lid ) {
// let y = x where x is known
// remove x, insert y
self . set . insert ( consume_pat . id ) ;
self . set . remove ( & lid ) ;
}
}
}
2016-01-04 04:26:12 +00:00
fn borrow ( & mut self , borrow_id : NodeId , _ : Span , cmt : cmt < ' tcx > , _ : ty ::Region , _ : ty ::BorrowKind ,
2015-12-04 10:12:53 +00:00
loan_cause : LoanCause ) {
if let Categorization ::Local ( lid ) = cmt . cat {
if self . set . contains ( & lid ) {
if let Some ( & AutoAdjustment ::AdjustDerefRef ( adj ) ) = self . cx
. tcx
. tables
. borrow ( )
. adjustments
. get ( & borrow_id ) {
if LoanCause ::AutoRef = = loan_cause {
// x.foo()
if adj . autoderefs < = 0 {
self . set . remove ( & lid ) ; // Used without autodereffing (i.e. x.clone())
}
} else {
self . cx . sess ( ) . span_bug ( cmt . span , " Unknown adjusted AutoRef " ) ;
}
} else if LoanCause ::AddrOf = = loan_cause {
// &x
2016-01-04 04:26:12 +00:00
if let Some ( & AutoAdjustment ::AdjustDerefRef ( adj ) ) = self . cx
. tcx
. tables
. borrow ( )
. adjustments
. get ( & self . cx
. tcx
. map
. get_parent_node ( borrow_id ) ) {
2015-12-04 10:12:53 +00:00
if adj . autoderefs < = 1 {
// foo(&x) where no extra autoreffing is happening
self . set . remove ( & lid ) ;
}
}
} else if LoanCause ::MatchDiscriminant = = loan_cause {
self . set . remove ( & lid ) ; // `match x` can move
}
// do nothing for matches, etc. These can't escape
}
}
}
fn decl_without_init ( & mut self , _ : NodeId , _ : Span ) { }
2016-01-04 04:26:12 +00:00
fn mutate ( & mut self , _ : NodeId , _ : Span , _ : cmt < ' tcx > , _ : MutateMode ) { }
2015-12-04 10:12:53 +00:00
}