]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/escape.rs
Merge pull request #1129 from oli-obk/needless_ref
[rust.git] / clippy_lints / src / escape.rs
1 use rustc::hir::*;
2 use rustc::hir::intravisit as visit;
3 use rustc::hir::map::Node::{NodeExpr, NodeStmt};
4 use rustc::infer::InferCtxt;
5 use rustc::lint::*;
6 use rustc::middle::expr_use_visitor::*;
7 use rustc::middle::mem_categorization::{cmt, Categorization};
8 use rustc::ty::adjustment::AutoAdjustment;
9 use rustc::ty;
10 use rustc::ty::layout::TargetDataLayout;
11 use rustc::util::nodemap::NodeSet;
12 use syntax::ast::NodeId;
13 use syntax::codemap::Span;
14 use utils::span_lint;
15
16 pub struct Pass {
17     pub too_large_for_stack: u64,
18 }
19
20 /// **What it does:** This lint checks for usage of `Box<T>` where an unboxed `T` would work fine.
21 ///
22 /// **Why is this bad?** This is an unnecessary allocation, and bad for performance. It is only
23 /// necessary to allocate if you wish to move the box into something.
24 ///
25 /// **Known problems:** None
26 ///
27 /// **Example:**
28 ///
29 /// ```rust
30 /// fn main() {
31 ///     let x = Box::new(1);
32 ///     foo(*x);
33 ///     println!("{}", *x);
34 /// }
35 /// ```
36 declare_lint! {
37     pub BOXED_LOCAL, Warn, "using `Box<T>` where unnecessary"
38 }
39
40 fn is_non_trait_box(ty: ty::Ty) -> bool {
41     match ty.sty {
42         ty::TyBox(inner) => !inner.is_trait(),
43         _ => false,
44     }
45 }
46
47 struct EscapeDelegate<'a, 'tcx: 'a+'gcx, 'gcx: 'a> {
48     tcx: ty::TyCtxt<'a, 'tcx, 'tcx>,
49     set: NodeSet,
50     infcx: &'a InferCtxt<'a, 'gcx, 'gcx>,
51     target: TargetDataLayout,
52     too_large_for_stack: u64,
53 }
54
55 impl LintPass for Pass {
56     fn get_lints(&self) -> LintArray {
57         lint_array!(BOXED_LOCAL)
58     }
59 }
60
61 impl LateLintPass for Pass {
62     fn check_fn(&mut self, cx: &LateContext, _: visit::FnKind, decl: &FnDecl, body: &Block, _: Span, id: NodeId) {
63         let param_env = ty::ParameterEnvironment::for_item(cx.tcx, id);
64
65         let infcx = cx.tcx.borrowck_fake_infer_ctxt(param_env);
66
67         // we store the infcx because it is expensive to recreate
68         // the context each time.
69         let mut v = EscapeDelegate {
70             tcx: cx.tcx,
71             set: NodeSet(),
72             infcx: &infcx,
73             target: TargetDataLayout::parse(cx.sess()),
74             too_large_for_stack: self.too_large_for_stack,
75         };
76
77         {
78             let mut vis = ExprUseVisitor::new(&mut v, &infcx);
79             vis.walk_fn(decl, body);
80         }
81
82         for node in v.set {
83             span_lint(cx,
84                       BOXED_LOCAL,
85                       cx.tcx.map.span(node),
86                       "local variable doesn't need to be boxed here");
87         }
88     }
89 }
90
91 impl<'a, 'tcx: 'a+'gcx, 'gcx: 'a> Delegate<'tcx> for EscapeDelegate<'a, 'tcx, 'gcx> {
92     fn consume(&mut self, _: NodeId, _: Span, cmt: cmt<'tcx>, mode: ConsumeMode) {
93         if let Categorization::Local(lid) = cmt.cat {
94             if self.set.contains(&lid) {
95                 if let Move(DirectRefMove) = mode {
96                     // moved out or in. clearly can't be localized
97                     self.set.remove(&lid);
98                 }
99             }
100         }
101     }
102     fn matched_pat(&mut self, _: &Pat, _: cmt<'tcx>, _: MatchMode) {}
103     fn consume_pat(&mut self, consume_pat: &Pat, cmt: cmt<'tcx>, _: ConsumeMode) {
104         let map = &self.tcx.map;
105         if map.is_argument(consume_pat.id) {
106             // Skip closure arguments
107             if let Some(NodeExpr(..)) = map.find(map.get_parent_node(consume_pat.id)) {
108                 return;
109             }
110             if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
111                 self.set.insert(consume_pat.id);
112             }
113             return;
114         }
115         if let Categorization::Rvalue(..) = cmt.cat {
116             if let Some(NodeStmt(st)) = map.find(map.get_parent_node(cmt.id)) {
117                 if let StmtDecl(ref decl, _) = st.node {
118                     if let DeclLocal(ref loc) = decl.node {
119                         if let Some(ref ex) = loc.init {
120                             if let ExprBox(..) = ex.node {
121                                 if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
122                                     // let x = box (...)
123                                     self.set.insert(consume_pat.id);
124                                 }
125                                 // TODO Box::new
126                                 // TODO vec![]
127                                 // TODO "foo".to_owned() and friends
128                             }
129                         }
130                     }
131                 }
132             }
133         }
134         if let Categorization::Local(lid) = cmt.cat {
135             if self.set.contains(&lid) {
136                 // let y = x where x is known
137                 // remove x, insert y
138                 self.set.insert(consume_pat.id);
139                 self.set.remove(&lid);
140             }
141         }
142
143     }
144     fn borrow(&mut self, borrow_id: NodeId, _: Span, cmt: cmt<'tcx>, _: ty::Region, _: ty::BorrowKind,
145               loan_cause: LoanCause) {
146
147         if let Categorization::Local(lid) = cmt.cat {
148             if self.set.contains(&lid) {
149                 if let Some(&AutoAdjustment::AdjustDerefRef(adj)) = self.tcx
150                                                                         .tables
151                                                                         .borrow()
152                                                                         .adjustments
153                                                                         .get(&borrow_id) {
154                     if LoanCause::AutoRef == loan_cause {
155                         // x.foo()
156                         if adj.autoderefs == 0 {
157                             self.set.remove(&lid); // Used without autodereffing (i.e. x.clone())
158                         }
159                     } else {
160                         span_bug!(cmt.span, "Unknown adjusted AutoRef");
161                     }
162                 } else if LoanCause::AddrOf == loan_cause {
163                     // &x
164                     if let Some(&AutoAdjustment::AdjustDerefRef(adj)) = self.tcx
165                                                                             .tables
166                                                                             .borrow()
167                                                                             .adjustments
168                                                                             .get(&self.tcx
169                                                                                       .map
170                                                                                       .get_parent_node(borrow_id)) {
171                         if adj.autoderefs <= 1 {
172                             // foo(&x) where no extra autoreffing is happening
173                             self.set.remove(&lid);
174                         }
175                     }
176
177                 } else if LoanCause::MatchDiscriminant == loan_cause {
178                     self.set.remove(&lid); // `match x` can move
179                 }
180                 // do nothing for matches, etc. These can't escape
181             }
182         }
183     }
184     fn decl_without_init(&mut self, _: NodeId, _: Span) {}
185     fn mutate(&mut self, _: NodeId, _: Span, _: cmt<'tcx>, _: MutateMode) {}
186 }
187
188 impl<'a, 'tcx: 'a+'gcx, 'gcx: 'a> EscapeDelegate<'a, 'tcx, 'gcx> {
189     fn is_large_box(&self, ty: ty::Ty<'gcx>) -> bool {
190         // Large types need to be boxed to avoid stack
191         // overflows.
192         match ty.sty {
193             ty::TyBox(inner) => {
194                 if let Ok(layout) = inner.layout(self.infcx) {
195                     let size = layout.size(&self.target);
196                     size.bytes() > self.too_large_for_stack
197                 } else {
198                     false
199                 }
200             },
201             _ => false,
202         }
203     }
204 }