]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/indexing_slicing.rs
Auto merge of #5003 - JohnTitor:rustup, r=flip1995
[rust.git] / clippy_lints / src / indexing_slicing.rs
1 //! lint on indexing and slicing operations
2
3 use crate::consts::{constant, Constant};
4 use crate::utils;
5 use crate::utils::higher;
6 use crate::utils::higher::Range;
7 use rustc::declare_lint_pass;
8 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
9 use rustc::ty;
10 use rustc_hir::*;
11 use rustc_session::declare_tool_lint;
12 use syntax::ast::RangeLimits;
13
14 declare_clippy_lint! {
15     /// **What it does:** Checks for out of bounds array indexing with a constant
16     /// index.
17     ///
18     /// **Why is this bad?** This will always panic at runtime.
19     ///
20     /// **Known problems:** Hopefully none.
21     ///
22     /// **Example:**
23     /// ```no_run
24     /// # #![allow(const_err)]
25     /// let x = [1, 2, 3, 4];
26     ///
27     /// // Bad
28     /// x[9];
29     /// &x[2..9];
30     ///
31     /// // Good
32     /// x[0];
33     /// x[3];
34     /// ```
35     pub OUT_OF_BOUNDS_INDEXING,
36     correctness,
37     "out of bounds constant indexing"
38 }
39
40 declare_clippy_lint! {
41     /// **What it does:** Checks for usage of indexing or slicing. Arrays are special cases, this lint
42     /// does report on arrays if we can tell that slicing operations are in bounds and does not
43     /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint.
44     ///
45     /// **Why is this bad?** Indexing and slicing can panic at runtime and there are
46     /// safe alternatives.
47     ///
48     /// **Known problems:** Hopefully none.
49     ///
50     /// **Example:**
51     /// ```rust,no_run
52     /// // Vector
53     /// let x = vec![0; 5];
54     ///
55     /// // Bad
56     /// x[2];
57     /// &x[2..100];
58     /// &x[2..];
59     /// &x[..100];
60     ///
61     /// // Good
62     /// x.get(2);
63     /// x.get(2..100);
64     /// x.get(2..);
65     /// x.get(..100);
66     ///
67     /// // Array
68     /// let y = [0, 1, 2, 3];
69     ///
70     /// // Bad
71     /// &y[10..100];
72     /// &y[10..];
73     /// &y[..100];
74     ///
75     /// // Good
76     /// &y[2..];
77     /// &y[..2];
78     /// &y[0..3];
79     /// y.get(10);
80     /// y.get(10..100);
81     /// y.get(10..);
82     /// y.get(..100);
83     /// ```
84     pub INDEXING_SLICING,
85     restriction,
86     "indexing/slicing usage"
87 }
88
89 declare_lint_pass!(IndexingSlicing => [INDEXING_SLICING, OUT_OF_BOUNDS_INDEXING]);
90
91 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for IndexingSlicing {
92     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
93         if let ExprKind::Index(ref array, ref index) = &expr.kind {
94             let ty = cx.tables.expr_ty(array);
95             if let Some(range) = higher::range(cx, index) {
96                 // Ranged indexes, i.e., &x[n..m], &x[n..], &x[..n] and &x[..]
97                 if let ty::Array(_, s) = ty.kind {
98                     let size: u128 = s.eval_usize(cx.tcx, cx.param_env).into();
99
100                     let const_range = to_const_range(cx, range, size);
101
102                     if let (Some(start), _) = const_range {
103                         if start > size {
104                             utils::span_lint(
105                                 cx,
106                                 OUT_OF_BOUNDS_INDEXING,
107                                 range.start.map_or(expr.span, |start| start.span),
108                                 "range is out of bounds",
109                             );
110                             return;
111                         }
112                     }
113
114                     if let (_, Some(end)) = const_range {
115                         if end > size {
116                             utils::span_lint(
117                                 cx,
118                                 OUT_OF_BOUNDS_INDEXING,
119                                 range.end.map_or(expr.span, |end| end.span),
120                                 "range is out of bounds",
121                             );
122                             return;
123                         }
124                     }
125
126                     if let (Some(_), Some(_)) = const_range {
127                         // early return because both start and end are constants
128                         // and we have proven above that they are in bounds
129                         return;
130                     }
131                 }
132
133                 let help_msg = match (range.start, range.end) {
134                     (None, Some(_)) => "Consider using `.get(..n)`or `.get_mut(..n)` instead",
135                     (Some(_), None) => "Consider using `.get(n..)` or .get_mut(n..)` instead",
136                     (Some(_), Some(_)) => "Consider using `.get(n..m)` or `.get_mut(n..m)` instead",
137                     (None, None) => return, // [..] is ok.
138                 };
139
140                 utils::span_help_and_lint(cx, INDEXING_SLICING, expr.span, "slicing may panic.", help_msg);
141             } else {
142                 // Catchall non-range index, i.e., [n] or [n << m]
143                 if let ty::Array(..) = ty.kind {
144                     // Index is a constant uint.
145                     if let Some(..) = constant(cx, cx.tables, index) {
146                         // Let rustc's `const_err` lint handle constant `usize` indexing on arrays.
147                         return;
148                     }
149                 }
150
151                 utils::span_help_and_lint(
152                     cx,
153                     INDEXING_SLICING,
154                     expr.span,
155                     "indexing may panic.",
156                     "Consider using `.get(n)` or `.get_mut(n)` instead",
157                 );
158             }
159         }
160     }
161 }
162
163 /// Returns a tuple of options with the start and end (exclusive) values of
164 /// the range. If the start or end is not constant, None is returned.
165 fn to_const_range<'a, 'tcx>(
166     cx: &LateContext<'a, 'tcx>,
167     range: Range<'_>,
168     array_size: u128,
169 ) -> (Option<u128>, Option<u128>) {
170     let s = range.start.map(|expr| constant(cx, cx.tables, expr).map(|(c, _)| c));
171     let start = match s {
172         Some(Some(Constant::Int(x))) => Some(x),
173         Some(_) => None,
174         None => Some(0),
175     };
176
177     let e = range.end.map(|expr| constant(cx, cx.tables, expr).map(|(c, _)| c));
178     let end = match e {
179         Some(Some(Constant::Int(x))) => {
180             if range.limits == RangeLimits::Closed {
181                 Some(x + 1)
182             } else {
183                 Some(x)
184             }
185         },
186         Some(_) => None,
187         None => Some(array_size),
188     };
189
190     (start, end)
191 }