Skip to content

Commit 999b300

Browse files
committed
new lint: string-slice
1 parent 73a443d commit 999b300

File tree

6 files changed

+106
-35
lines changed

6 files changed

+106
-35
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3000,6 +3000,7 @@ Released 2018-09-13
30003000
[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
30013001
[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
30023002
[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
3003+
[`string_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_slice
30033004
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
30043005
[`strlen_on_c_strings`]: https://rust-lang.github.io/rust-clippy/master/index.html#strlen_on_c_strings
30053006
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ store.register_lints(&[
431431
strings::STRING_ADD_ASSIGN,
432432
strings::STRING_FROM_UTF8_AS_BYTES,
433433
strings::STRING_LIT_AS_BYTES,
434+
strings::STRING_SLICE,
434435
strings::STRING_TO_STRING,
435436
strings::STR_TO_STRING,
436437
strlen_on_c_strings::STRLEN_ON_C_STRINGS,

clippy_lints/src/lib.register_restriction.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
5353
LintId::of(shadow::SHADOW_SAME),
5454
LintId::of(shadow::SHADOW_UNRELATED),
5555
LintId::of(strings::STRING_ADD),
56+
LintId::of(strings::STRING_SLICE),
5657
LintId::of(strings::STRING_TO_STRING),
5758
LintId::of(strings::STR_TO_STRING),
5859
LintId::of(types::RC_BUFFER),

clippy_lints/src/strings.rs

+71-35
Original file line numberDiff line numberDiff line change
@@ -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

112135
impl<'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
}

tests/ui/string_slice.rs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#[warn(clippy::string_slice)]
2+
#[allow(clippy::no_effect)]
3+
4+
fn main() {
5+
&"Ölkanne"[1..];
6+
let m = "Mötörhead";
7+
&m[2..5];
8+
let s = String::from(m);
9+
&s[0..2];
10+
}

tests/ui/string_slice.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: indexing into a string may panic if the index is within a UTF-8 character
2+
--> $DIR/string_slice.rs:5:6
3+
|
4+
LL | &"Ölkanne"[1..];
5+
| ^^^^^^^^^^^^^^
6+
|
7+
= note: `-D clippy::string-slice` implied by `-D warnings`
8+
9+
error: indexing into a string may panic if the index is within a UTF-8 character
10+
--> $DIR/string_slice.rs:7:6
11+
|
12+
LL | &m[2..5];
13+
| ^^^^^^^
14+
15+
error: indexing into a string may panic if the index is within a UTF-8 character
16+
--> $DIR/string_slice.rs:9:6
17+
|
18+
LL | &s[0..2];
19+
| ^^^^^^^
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)