Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit d8752db

Browse files
committed
New lint
1 parent 4825666 commit d8752db

File tree

9 files changed

+159
-3
lines changed

9 files changed

+159
-3
lines changed

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
503503
crate::methods::WAKER_CLONE_WAKE_INFO,
504504
crate::methods::WRONG_SELF_CONVENTION_INFO,
505505
crate::methods::ZST_OFFSET_INFO,
506+
crate::methods::SLICE_AS_BYTES_INFO,
506507
crate::min_ident_chars::MIN_IDENT_CHARS_INFO,
507508
crate::minmax::MIN_MAX_INFO,
508509
crate::misc::SHORT_CIRCUIT_STATEMENT_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 slice_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.72.0"]
4390+
pub SLICE_AS_BYTES,
4391+
pedantic,
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+
SLICE_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+
slice_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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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::SLICE_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+
{
14+
let ty = cx.typeck_results().expr_ty(indexed).peel_refs();
15+
if ty.is_str() || is_type_lang_item(cx, ty, LangItem::String) {
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+
SLICE_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+
}
30+
}

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

tests/ui/slice_as_bytes.stderr

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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

0 commit comments

Comments
 (0)