@@ -107,51 +107,87 @@ declare_clippy_lint! {
107107 "calling `as_bytes` on a string literal instead of using a byte string literal"
108108}
109109
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 ] ) ;
111134
112135impl < ' tcx > LateLintPass < ' tcx > for StringAdd {
113136 fn check_expr ( & mut self , cx : & LateContext < ' tcx > , e : & ' tcx Expr < ' _ > ) {
114137 if in_external_macro ( cx. sess ( ) , e. span ) {
115138 return ;
116139 }
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+ }
134157 }
135158 }
136159 }
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+ ) ;
137166 }
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+ _ => { } ,
155191 }
156192 }
157193}
0 commit comments