]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/escape.rs
Merge branch 'macro-use' into HEAD
[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::lint::*;
5 use rustc::{declare_lint, lint_array};
6 use rustc::middle::expr_use_visitor::*;
7 use rustc::middle::mem_categorization::{cmt_, Categorization};
8 use rustc::ty::{self, Ty};
9 use rustc::ty::layout::LayoutOf;
10 use rustc::util::nodemap::NodeSet;
11 use syntax::ast::NodeId;
12 use syntax::codemap::Span;
13 use crate::utils::span_lint;
14
15 pub struct Pass {
16     pub too_large_for_stack: u64,
17 }
18
19 /// **What it does:** Checks for usage of `Box<T>` where an unboxed `T` would
20 /// work fine.
21 ///
22 /// **Why is this bad?** This is an unnecessary allocation, and bad for
23 /// performance. It is only necessary to allocate if you wish to move the box
24 /// into something.
25 ///
26 /// **Known problems:** None.
27 ///
28 /// **Example:**
29 /// ```rust
30 /// fn main() {
31 ///     let x = Box::new(1);
32 ///     foo(*x);
33 ///     println!("{}", *x);
34 /// }
35 /// ```
36 declare_clippy_lint! {
37     pub BOXED_LOCAL,
38     perf,
39     "using `Box<T>` where unnecessary"
40 }
41
42 fn is_non_trait_box(ty: Ty) -> bool {
43     ty.is_box() && !ty.boxed_ty().is_trait()
44 }
45
46 struct EscapeDelegate<'a, 'tcx: 'a> {
47     cx: &'a LateContext<'a, 'tcx>,
48     set: NodeSet,
49     too_large_for_stack: u64,
50 }
51
52 impl LintPass for Pass {
53     fn get_lints(&self) -> LintArray {
54         lint_array!(BOXED_LOCAL)
55     }
56 }
57
58 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Pass {
59     fn check_fn(
60         &mut self,
61         cx: &LateContext<'a, 'tcx>,
62         _: visit::FnKind<'tcx>,
63         _: &'tcx FnDecl,
64         body: &'tcx Body,
65         _: Span,
66         node_id: NodeId,
67     ) {
68         let fn_def_id = cx.tcx.hir.local_def_id(node_id);
69         let mut v = EscapeDelegate {
70             cx,
71             set: NodeSet(),
72             too_large_for_stack: self.too_large_for_stack,
73         };
74
75         let region_scope_tree = &cx.tcx.region_scope_tree(fn_def_id);
76         ExprUseVisitor::new(&mut v, cx.tcx, cx.param_env, region_scope_tree, cx.tables, None).consume_body(body);
77
78         for node in v.set {
79             span_lint(
80                 cx,
81                 BOXED_LOCAL,
82                 cx.tcx.hir.span(node),
83                 "local variable doesn't need to be boxed here",
84             );
85         }
86     }
87 }
88
89 impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> {
90     fn consume(&mut self, _: NodeId, _: Span, cmt: &cmt_<'tcx>, mode: ConsumeMode) {
91         if let Categorization::Local(lid) = cmt.cat {
92             if let Move(DirectRefMove) = mode {
93                 // moved out or in. clearly can't be localized
94                 self.set.remove(&lid);
95             }
96         }
97     }
98     fn matched_pat(&mut self, _: &Pat, _: &cmt_<'tcx>, _: MatchMode) {}
99     fn consume_pat(&mut self, consume_pat: &Pat, cmt: &cmt_<'tcx>, _: ConsumeMode) {
100         let map = &self.cx.tcx.hir;
101         if map.is_argument(consume_pat.id) {
102             // Skip closure arguments
103             if let Some(NodeExpr(..)) = map.find(map.get_parent_node(consume_pat.id)) {
104                 return;
105             }
106             if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
107                 self.set.insert(consume_pat.id);
108             }
109             return;
110         }
111         if let Categorization::Rvalue(..) = cmt.cat {
112             let id = map.hir_to_node_id(cmt.hir_id);
113             if let Some(NodeStmt(st)) = map.find(map.get_parent_node(id)) {
114                 if let StmtKind::Decl(ref decl, _) = st.node {
115                     if let DeclKind::Local(ref loc) = decl.node {
116                         if let Some(ref ex) = loc.init {
117                             if let ExprKind::Box(..) = ex.node {
118                                 if is_non_trait_box(cmt.ty) && !self.is_large_box(cmt.ty) {
119                                     // let x = box (...)
120                                     self.set.insert(consume_pat.id);
121                                 }
122                                 // TODO Box::new
123                                 // TODO vec![]
124                                 // TODO "foo".to_owned() and friends
125                             }
126                         }
127                     }
128                 }
129             }
130         }
131         if let Categorization::Local(lid) = cmt.cat {
132             if self.set.contains(&lid) {
133                 // let y = x where x is known
134                 // remove x, insert y
135                 self.set.insert(consume_pat.id);
136                 self.set.remove(&lid);
137             }
138         }
139     }
140     fn borrow(&mut self, _: NodeId, _: Span, cmt: &cmt_<'tcx>, _: ty::Region, _: ty::BorrowKind, loan_cause: LoanCause) {
141         if let Categorization::Local(lid) = cmt.cat {
142             match loan_cause {
143                 // x.foo()
144                 // Used without autodereffing (i.e. x.clone())
145                 LoanCause::AutoRef |
146
147                 // &x
148                 // foo(&x) where no extra autoreffing is happening
149                 LoanCause::AddrOf |
150
151                 // `match x` can move
152                 LoanCause::MatchDiscriminant => {
153                     self.set.remove(&lid);
154                 }
155
156                 // do nothing for matches, etc. These can't escape
157                 _ => {}
158             }
159         }
160     }
161     fn decl_without_init(&mut self, _: NodeId, _: Span) {}
162     fn mutate(&mut self, _: NodeId, _: Span, _: &cmt_<'tcx>, _: MutateMode) {}
163 }
164
165 impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> {
166     fn is_large_box(&self, ty: Ty<'tcx>) -> bool {
167         // Large types need to be boxed to avoid stack
168         // overflows.
169         if ty.is_box() {
170             self.cx.layout_of(ty.boxed_ty()).ok().map_or(0, |l| l.size.bytes()) > self.too_large_for_stack
171         } else {
172             false
173         }
174     }
175 }