Skip to content

Commit 99480fa

Browse files
committed
Rework new slice_as_bytes lint
1 parent 37f4c17 commit 99480fa

File tree

7 files changed

+158
-0
lines changed

7 files changed

+158
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5205,6 +5205,7 @@ Released 2018-09-13
52055205
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
52065206
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
52075207
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
5208+
[`slice_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#slice_as_bytes
52085209
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
52095210
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
52105211
[`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
@@ -398,6 +398,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
398398
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
399399
crate::methods::SINGLE_CHAR_PATTERN_INFO,
400400
crate::methods::SKIP_WHILE_NEXT_INFO,
401+
crate::methods::SLICE_AS_BYTES_INFO,
401402
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
402403
crate::methods::STRING_EXTEND_CHARS_INFO,
403404
crate::methods::SUSPICIOUS_COMMAND_ARG_SPACE_INFO,

clippy_lints/src/methods/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ mod single_char_insert_string;
8181
mod single_char_pattern;
8282
mod single_char_push_string;
8383
mod skip_while_next;
84+
mod slice_as_bytes;
8485
mod stable_sort_primitive;
8586
mod str_splitn;
8687
mod string_extend_chars;
@@ -3316,6 +3317,26 @@ declare_clippy_lint! {
33163317
"checks for usage of `Iterator::fold` with a type that implements `Try`"
33173318
}
33183319

3320+
declare_clippy_lint! {
3321+
/// Checks for string slices immediantly followed by `as_bytes`.
3322+
/// ### Why is this bad?
3323+
/// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic.
3324+
/// ### Example
3325+
/// ```rust
3326+
/// let s = "Lorem ipsum";
3327+
/// s[1..5].as_bytes();
3328+
/// ```
3329+
/// Use instead:
3330+
/// ```rust
3331+
/// let s = "Lorem ipsum";
3332+
/// &s.as_bytes()[1..5];
3333+
/// ```
3334+
#[clippy::version = "1.72.0"]
3335+
pub SLICE_AS_BYTES,
3336+
perf,
3337+
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
3338+
}
3339+
33193340
pub struct Methods {
33203341
avoid_breaking_exported_api: bool,
33213342
msrv: Msrv,
@@ -3448,6 +3469,7 @@ impl_lint_pass!(Methods => [
34483469
UNNECESSARY_LITERAL_UNWRAP,
34493470
DRAIN_COLLECT,
34503471
MANUAL_TRY_FOLD,
3472+
SLICE_AS_BYTES,
34513473
]);
34523474

34533475
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3656,6 +3678,9 @@ impl Methods {
36563678
("arg", [arg]) => {
36573679
suspicious_command_arg_space::check(cx, recv, arg, span);
36583680
}
3681+
("as_bytes",[]) => {
3682+
slice_as_bytes::check(cx, expr, recv);
3683+
}
36593684
("as_deref" | "as_deref_mut", []) => {
36603685
needless_option_as_deref::check(cx, expr, recv, name);
36613686
},
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)