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