Skip to content

Commit 5dd77b2

Browse files
committed
Rework new slice_as_bytes lint
1 parent 06b444b commit 5dd77b2

File tree

7 files changed

+160
-1
lines changed

7 files changed

+160
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5171,6 +5171,7 @@ Released 2018-09-13
51715171
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
51725172
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
51735173
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
5174+
[`slice_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#slice_as_bytes
51745175
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
51755176
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
51765177
[`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
@@ -397,6 +397,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
397397
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
398398
crate::methods::SINGLE_CHAR_PATTERN_INFO,
399399
crate::methods::SKIP_WHILE_NEXT_INFO,
400+
crate::methods::SLICE_AS_BYTES_INFO,
400401
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
401402
crate::methods::STRING_EXTEND_CHARS_INFO,
402403
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ mod single_char_insert_string;
8080
mod single_char_pattern;
8181
mod single_char_push_string;
8282
mod skip_while_next;
83+
mod slice_as_bytes;
8384
mod stable_sort_primitive;
8485
mod str_splitn;
8586
mod string_extend_chars;
@@ -3284,6 +3285,27 @@ declare_clippy_lint! {
32843285
"calling `.drain(..).collect()` to move all elements into a new collection"
32853286
}
32863287

3288+
declare_clippy_lint! {
3289+
/// ### What it does
3290+
/// Checks for string slices immediantly followed by `as_bytes`.
3291+
/// ### Why is this bad?
3292+
/// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic.
3293+
/// ### Example
3294+
/// ```rust
3295+
/// let s = "Lorem ipsum";
3296+
/// s[1..5].as_bytes();
3297+
/// ```
3298+
/// Use instead:
3299+
/// ```rust
3300+
/// let s = "Lorem ipsum";
3301+
/// &s.as_bytes()[1..5];
3302+
/// ```
3303+
#[clippy::version = "1.72.0"]
3304+
pub SLICE_AS_BYTES,
3305+
perf,
3306+
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
3307+
}
3308+
32873309
pub struct Methods {
32883310
avoid_breaking_exported_api: bool,
32893311
msrv: Msrv,
@@ -3414,7 +3436,8 @@ impl_lint_pass!(Methods => [
34143436
CLEAR_WITH_DRAIN,
34153437
MANUAL_NEXT_BACK,
34163438
UNNECESSARY_LITERAL_UNWRAP,
3417-
DRAIN_COLLECT
3439+
DRAIN_COLLECT,
3440+
SLICE_AS_BYTES,
34183441
]);
34193442

34203443
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3623,6 +3646,9 @@ impl Methods {
36233646
("arg", [arg]) => {
36243647
suspicious_command_arg_space::check(cx, recv, arg, span);
36253648
}
3649+
("as_bytes",[]) => {
3650+
slice_as_bytes::check(cx, expr, recv);
3651+
}
36263652
("as_deref" | "as_deref_mut", []) => {
36273653
needless_option_as_deref::check(cx, expr, recv, name);
36283654
},
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet_with_applicability, ty::is_type_lang_item};
2+
use rustc_errors::Applicability;
3+
use rustc_hir::{
4+
Expr, ExprKind,
5+
LangItem::{self, Range, RangeFrom, RangeFull, RangeTo, RangeToInclusive},
6+
QPath,
7+
};
8+
use rustc_lint::LateContext;
9+
10+
use super::SLICE_AS_BYTES;
11+
12+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) {
13+
if let ExprKind::Index(indexed, index) = recv.kind {
14+
if let ExprKind::Struct(QPath::LangItem(Range | RangeFrom | RangeTo | RangeToInclusive | RangeFull, ..), ..) =
15+
index.kind
16+
{
17+
let ty = cx.typeck_results().expr_ty(indexed).peel_refs();
18+
let is_str = ty.is_str();
19+
let is_string = is_type_lang_item(cx, ty, LangItem::String);
20+
if is_str || is_string {
21+
let mut applicability = Applicability::MachineApplicable;
22+
let stringish = snippet_with_applicability(cx, indexed.span, "..", &mut applicability);
23+
let range = snippet_with_applicability(cx, index.span, "..", &mut applicability);
24+
let type_name = if is_str { "str" } else { "String" };
25+
span_lint_and_sugg(
26+
cx,
27+
SLICE_AS_BYTES,
28+
expr.span,
29+
&(format!(
30+
"slicing a {type_name} before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking"
31+
)),
32+
"try",
33+
format!("&{stringish}.as_bytes()[{range}]"),
34+
applicability,
35+
);
36+
}
37+
}
38+
}
39+
}

tests/ui/slice_as_bytes.fixed

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

tests/ui/slice_as_bytes.rs

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

tests/ui/slice_as_bytes.stderr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: slicing a str before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking
2+
--> $DIR/slice_as_bytes.rs:29:17
3+
|
4+
LL | let bytes = s[1..5].as_bytes();
5+
| ^^^^^^^^^^^^^^^^^^ help: try: `&s.as_bytes()[1..5]`
6+
|
7+
= note: `-D clippy::slice-as-bytes` implied by `-D warnings`
8+
9+
error: slicing a String before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking
10+
--> $DIR/slice_as_bytes.rs:30:17
11+
|
12+
LL | let bytes = string[1..].as_bytes();
13+
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&string.as_bytes()[1..]`
14+
15+
error: slicing a str before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking
16+
--> $DIR/slice_as_bytes.rs:31:17
17+
|
18+
LL | let bytes = "consectetur adipiscing"[..=5].as_bytes();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&"consectetur adipiscing".as_bytes()[..=5]`
20+
21+
error: aborting due to 3 previous errors
22+

0 commit comments

Comments
 (0)