@@ -107,51 +107,87 @@ declare_clippy_lint! {
107
107
"calling `as_bytes` on a string literal instead of using a byte string literal"
108
108
}
109
109
110
- declare_lint_pass ! ( StringAdd => [ STRING_ADD , STRING_ADD_ASSIGN ] ) ;
110
+ declare_clippy_lint ! {
111
+ /// ### What it does
112
+ /// Checks for slice operations on strings
113
+ ///
114
+ /// ### Why is this bad?
115
+ /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character
116
+ /// counts and string indices. This may lead to panics, and should warrant some test cases
117
+ /// containing wide UTF-8 characters. This lint is most useful in code that should avoid
118
+ /// panics at all costs.
119
+ ///
120
+ /// ### Known problems
121
+ /// Probably lots of false positives. If an index comes from a known valid position (e.g.
122
+ /// obtained via `char_indices` over the same string), it is totally OK.
123
+ ///
124
+ /// # Example
125
+ /// ```rust,should_panic
126
+ /// &"Ölkanne"[1..];
127
+ /// ```
128
+ pub STRING_SLICE ,
129
+ restriction,
130
+ "slicing a string"
131
+ }
132
+
133
+ declare_lint_pass ! ( StringAdd => [ STRING_ADD , STRING_ADD_ASSIGN , STRING_SLICE ] ) ;
111
134
112
135
impl < ' tcx > LateLintPass < ' tcx > for StringAdd {
113
136
fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
114
137
if in_external_macro ( cx. sess ( ) , e. span ) {
115
138
return ;
116
139
}
117
-
118
- if let ExprKind :: Binary (
119
- Spanned {
120
- node : BinOpKind :: Add , ..
121
- } ,
122
- left,
123
- _,
124
- ) = e . kind
125
- {
126
- if is_string ( cx, left ) {
127
- if ! is_lint_allowed ( cx, STRING_ADD_ASSIGN , e . hir_id ) {
128
- let parent = get_parent_expr ( cx , e ) ;
129
- if let Some ( p ) = parent {
130
- if let ExprKind :: Assign ( target , _ , _ ) = p . kind {
131
- // avoid duplicate matches
132
- if SpanlessEq :: new ( cx ) . eq_expr ( target , left ) {
133
- return ;
140
+ match e . kind {
141
+ ExprKind :: Binary (
142
+ Spanned {
143
+ node : BinOpKind :: Add , ..
144
+ } ,
145
+ left,
146
+ _,
147
+ ) => {
148
+ if is_string ( cx , left ) {
149
+ if ! is_lint_allowed ( cx, STRING_ADD_ASSIGN , e . hir_id ) {
150
+ let parent = get_parent_expr ( cx, e ) ;
151
+ if let Some ( p ) = parent {
152
+ if let ExprKind :: Assign ( target , _ , _ ) = p . kind {
153
+ // avoid duplicate matches
154
+ if SpanlessEq :: new ( cx ) . eq_expr ( target , left ) {
155
+ return ;
156
+ }
134
157
}
135
158
}
136
159
}
160
+ span_lint (
161
+ cx,
162
+ STRING_ADD ,
163
+ e. span ,
164
+ "you added something to a string. Consider using `String::push_str()` instead" ,
165
+ ) ;
137
166
}
138
- span_lint (
139
- cx,
140
- STRING_ADD ,
141
- e. span ,
142
- "you added something to a string. Consider using `String::push_str()` instead" ,
143
- ) ;
144
- }
145
- } else if let ExprKind :: Assign ( target, src, _) = e. kind {
146
- if is_string ( cx, target) && is_add ( cx, src, target) {
147
- span_lint (
148
- cx,
149
- STRING_ADD_ASSIGN ,
150
- e. span ,
151
- "you assigned the result of adding something to this string. Consider using \
152
- `String::push_str()` instead",
153
- ) ;
154
- }
167
+ } ,
168
+ ExprKind :: Assign ( target, src, _) => {
169
+ if is_string ( cx, target) && is_add ( cx, src, target) {
170
+ span_lint (
171
+ cx,
172
+ STRING_ADD_ASSIGN ,
173
+ e. span ,
174
+ "you assigned the result of adding something to this string. Consider using \
175
+ `String::push_str()` instead",
176
+ ) ;
177
+ }
178
+ } ,
179
+ ExprKind :: Index ( target, _idx) => {
180
+ let e_ty = cx. typeck_results ( ) . expr_ty ( target) . peel_refs ( ) ;
181
+ if matches ! ( e_ty. kind( ) , ty:: Str ) || is_type_diagnostic_item ( cx, e_ty, sym:: String ) {
182
+ span_lint (
183
+ cx,
184
+ STRING_SLICE ,
185
+ e. span ,
186
+ "indexing into a string may panic if the index is within a UTF-8 character" ,
187
+ ) ;
188
+ }
189
+ } ,
190
+ _ => { } ,
155
191
}
156
192
}
157
193
}
0 commit comments