Skip to content

Commit 85bbba6

Browse files
authored
New lint sliced_string_as_bytes (rust-lang#14002)
resurrection of rust-lang/rust-clippy#10984 fixes rust-lang/rust-clippy#10981 changelog: [`sliced_string_as_bytes`]: add new lint `sliced_string_as_bytes`
2 parents ac805d4 + beeb6f7 commit 85bbba6

10 files changed

+158
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6066,6 +6066,7 @@ Released 2018-09-13
60666066
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
60676067
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
60686068
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
6069+
[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes
60696070
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
60706071
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
60716072
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
468468
crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO,
469469
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
470470
crate::methods::SKIP_WHILE_NEXT_INFO,
471+
crate::methods::SLICED_STRING_AS_BYTES_INFO,
471472
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
472473
crate::methods::STRING_EXTEND_CHARS_INFO,
473474
crate::methods::STRING_LIT_CHARS_ANY_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ mod single_char_add_str;
102102
mod single_char_insert_string;
103103
mod single_char_push_string;
104104
mod skip_while_next;
105+
mod sliced_string_as_bytes;
105106
mod stable_sort_primitive;
106107
mod str_split;
107108
mod str_splitn;
@@ -4363,6 +4364,34 @@ declare_clippy_lint! {
43634364
"detect `repeat().take()` that can be replaced with `repeat_n()`"
43644365
}
43654366

4367+
declare_clippy_lint! {
4368+
/// ### What it does
4369+
/// Checks for string slices immediantly followed by `as_bytes`.
4370+
///
4371+
/// ### Why is this bad?
4372+
/// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic.
4373+
///
4374+
/// ### Known problems
4375+
/// In some cases, the UTF-8 validation and potential panic from string slicing may be required for
4376+
/// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character
4377+
/// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred.
4378+
///
4379+
/// ### Example
4380+
/// ```rust
4381+
/// let s = "Lorem ipsum";
4382+
/// s[1..5].as_bytes();
4383+
/// ```
4384+
/// Use instead:
4385+
/// ```rust
4386+
/// let s = "Lorem ipsum";
4387+
/// &s.as_bytes()[1..5];
4388+
/// ```
4389+
#[clippy::version = "1.86.0"]
4390+
pub SLICED_STRING_AS_BYTES,
4391+
perf,
4392+
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
4393+
}
4394+
43664395
pub struct Methods {
43674396
avoid_breaking_exported_api: bool,
43684397
msrv: Msrv,
@@ -4531,6 +4560,7 @@ impl_lint_pass!(Methods => [
45314560
DOUBLE_ENDED_ITERATOR_LAST,
45324561
USELESS_NONZERO_NEW_UNCHECKED,
45334562
MANUAL_REPEAT_N,
4563+
SLICED_STRING_AS_BYTES,
45344564
]);
45354565

45364566
/// Extracts a method call name, args, and `Span` of the method name.
@@ -4798,6 +4828,7 @@ impl Methods {
47984828
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
47994829
redundant_as_str::check(cx, expr, recv, as_str_span, span);
48004830
}
4831+
sliced_string_as_bytes::check(cx, expr, recv);
48014832
},
48024833
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
48034834
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use clippy_utils::diagnostics::span_lint_and_sugg;
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::ty::is_type_lang_item;
4+
use rustc_errors::Applicability;
5+
use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal};
6+
use rustc_lint::LateContext;
7+
8+
use super::SLICED_STRING_AS_BYTES;
9+
10+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) {
11+
if let ExprKind::Index(indexed, index, _) = recv.kind
12+
&& is_range_literal(index)
13+
&& let ty = cx.typeck_results().expr_ty(indexed).peel_refs()
14+
&& (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String))
15+
{
16+
let mut applicability = Applicability::MaybeIncorrect;
17+
let stringish = snippet_with_applicability(cx, indexed.span, "_", &mut applicability);
18+
let range = snippet_with_applicability(cx, index.span, "_", &mut applicability);
19+
span_lint_and_sugg(
20+
cx,
21+
SLICED_STRING_AS_BYTES,
22+
expr.span,
23+
"calling `as_bytes` after slicing a string",
24+
"try",
25+
format!("&{stringish}.as_bytes()[{range}]"),
26+
applicability,
27+
);
28+
}
29+
}

tests/ui/bytes_nth.fixed

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![allow(clippy::unnecessary_operation)]
2+
#![allow(clippy::sliced_string_as_bytes)]
23
#![warn(clippy::bytes_nth)]
34

45
fn main() {

tests/ui/bytes_nth.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![allow(clippy::unnecessary_operation)]
2+
#![allow(clippy::sliced_string_as_bytes)]
23
#![warn(clippy::bytes_nth)]
34

45
fn main() {

tests/ui/bytes_nth.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: called `.bytes().nth()` on a `String`
2-
--> tests/ui/bytes_nth.rs:6:13
2+
--> tests/ui/bytes_nth.rs:7:13
33
|
44
LL | let _ = s.bytes().nth(3);
55
| ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3).copied()`
@@ -8,13 +8,13 @@ LL | let _ = s.bytes().nth(3);
88
= help: to override `-D warnings` add `#[allow(clippy::bytes_nth)]`
99

1010
error: called `.bytes().nth().unwrap()` on a `String`
11-
--> tests/ui/bytes_nth.rs:7:14
11+
--> tests/ui/bytes_nth.rs:8:14
1212
|
1313
LL | let _ = &s.bytes().nth(3).unwrap();
1414
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `s.as_bytes()[3]`
1515

1616
error: called `.bytes().nth()` on a `str`
17-
--> tests/ui/bytes_nth.rs:8:13
17+
--> tests/ui/bytes_nth.rs:9:13
1818
|
1919
LL | let _ = s[..].bytes().nth(3);
2020
| ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3).copied()`

tests/ui/sliced_string_as_bytes.fixed

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![allow(unused)]
2+
#![warn(clippy::sliced_string_as_bytes)]
3+
4+
use std::ops::{Index, Range};
5+
6+
struct Foo;
7+
8+
struct Bar;
9+
10+
impl Bar {
11+
fn as_bytes(&self) -> &[u8] {
12+
&[0, 1, 2, 3]
13+
}
14+
}
15+
16+
impl Index<Range<usize>> for Foo {
17+
type Output = Bar;
18+
19+
fn index(&self, _: Range<usize>) -> &Self::Output {
20+
&Bar
21+
}
22+
}
23+
24+
fn main() {
25+
let s = "Lorem ipsum";
26+
let string: String = "dolor sit amet".to_owned();
27+
28+
let bytes = &s.as_bytes()[1..5];
29+
let bytes = &string.as_bytes()[1..];
30+
let bytes = &"consectetur adipiscing".as_bytes()[..=5];
31+
32+
let f = Foo;
33+
let bytes = f[0..4].as_bytes();
34+
}

tests/ui/sliced_string_as_bytes.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#![allow(unused)]
2+
#![warn(clippy::sliced_string_as_bytes)]
3+
4+
use std::ops::{Index, Range};
5+
6+
struct Foo;
7+
8+
struct Bar;
9+
10+
impl Bar {
11+
fn as_bytes(&self) -> &[u8] {
12+
&[0, 1, 2, 3]
13+
}
14+
}
15+
16+
impl Index<Range<usize>> for Foo {
17+
type Output = Bar;
18+
19+
fn index(&self, _: Range<usize>) -> &Self::Output {
20+
&Bar
21+
}
22+
}
23+
24+
fn main() {
25+
let s = "Lorem ipsum";
26+
let string: String = "dolor sit amet".to_owned();
27+
28+
let bytes = s[1..5].as_bytes();
29+
let bytes = string[1..].as_bytes();
30+
let bytes = "consectetur adipiscing"[..=5].as_bytes();
31+
32+
let f = Foo;
33+
let bytes = f[0..4].as_bytes();
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: calling `as_bytes` after slicing a string
2+
--> tests/ui/sliced_string_as_bytes.rs:28:17
3+
|
4+
LL | let bytes = s[1..5].as_bytes();
5+
| ^^^^^^^^^^^^^^^^^^ help: try: `&s.as_bytes()[1..5]`
6+
|
7+
= note: `-D clippy::sliced-string-as-bytes` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::sliced_string_as_bytes)]`
9+
10+
error: calling `as_bytes` after slicing a string
11+
--> tests/ui/sliced_string_as_bytes.rs:29:17
12+
|
13+
LL | let bytes = string[1..].as_bytes();
14+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&string.as_bytes()[1..]`
15+
16+
error: calling `as_bytes` after slicing a string
17+
--> tests/ui/sliced_string_as_bytes.rs:30:17
18+
|
19+
LL | let bytes = "consectetur adipiscing"[..=5].as_bytes();
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&"consectetur adipiscing".as_bytes()[..=5]`
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)