Skip to content

New lint sliced_string_as_bytes #14002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 27, 2025
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6066,6 +6066,7 @@ Released 2018-09-13
[`size_of_in_element_count`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_in_element_count
[`size_of_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#size_of_ref
[`skip_while_next`]: https://rust-lang.github.io/rust-clippy/master/index.html#skip_while_next
[`sliced_string_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#sliced_string_as_bytes
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`stable_sort_primitive`]: https://rust-lang.github.io/rust-clippy/master/index.html#stable_sort_primitive
[`std_instead_of_alloc`]: https://rust-lang.github.io/rust-clippy/master/index.html#std_instead_of_alloc
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::methods::SHOULD_IMPLEMENT_TRAIT_INFO,
crate::methods::SINGLE_CHAR_ADD_STR_INFO,
crate::methods::SKIP_WHILE_NEXT_INFO,
crate::methods::SLICED_STRING_AS_BYTES_INFO,
crate::methods::STABLE_SORT_PRIMITIVE_INFO,
crate::methods::STRING_EXTEND_CHARS_INFO,
crate::methods::STRING_LIT_CHARS_ANY_INFO,
Expand Down
31 changes: 31 additions & 0 deletions clippy_lints/src/methods/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ mod single_char_add_str;
mod single_char_insert_string;
mod single_char_push_string;
mod skip_while_next;
mod sliced_string_as_bytes;
mod stable_sort_primitive;
mod str_split;
mod str_splitn;
Expand Down Expand Up @@ -4363,6 +4364,34 @@ declare_clippy_lint! {
"detect `repeat().take()` that can be replaced with `repeat_n()`"
}

declare_clippy_lint! {
/// ### What it does
/// Checks for string slices immediantly followed by `as_bytes`.
///
/// ### Why is this bad?
/// It involves doing an unnecessary UTF-8 alignment check which is less efficient, and can cause a panic.
///
/// ### Known problems
/// In some cases, the UTF-8 validation and potential panic from string slicing may be required for
/// the code's correctness. If you need to ensure the slice boundaries fall on valid UTF-8 character
/// boundaries, the original form (`s[1..5].as_bytes()`) should be preferred.
///
/// ### Example
/// ```rust
/// let s = "Lorem ipsum";
/// s[1..5].as_bytes();
/// ```
/// Use instead:
/// ```rust
/// let s = "Lorem ipsum";
/// &s.as_bytes()[1..5];
/// ```
#[clippy::version = "1.86.0"]
pub SLICED_STRING_AS_BYTES,
perf,
"slicing a string and immediately calling as_bytes is less efficient and can lead to panics"
}

pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
Expand Down Expand Up @@ -4531,6 +4560,7 @@ impl_lint_pass!(Methods => [
DOUBLE_ENDED_ITERATOR_LAST,
USELESS_NONZERO_NEW_UNCHECKED,
MANUAL_REPEAT_N,
SLICED_STRING_AS_BYTES,
]);

/// Extracts a method call name, args, and `Span` of the method name.
Expand Down Expand Up @@ -4798,6 +4828,7 @@ impl Methods {
if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) {
redundant_as_str::check(cx, expr, recv, as_str_span, span);
}
sliced_string_as_bytes::check(cx, expr, recv);
},
("as_mut", []) => useless_asref::check(cx, expr, "as_mut", recv),
("as_ptr", []) => manual_c_str_literals::check_as_ptr(cx, expr, recv, &self.msrv),
Expand Down
29 changes: 29 additions & 0 deletions clippy_lints/src/methods/sliced_string_as_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_lang_item;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal};
use rustc_lint::LateContext;

use super::SLICED_STRING_AS_BYTES;

pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) {
if let ExprKind::Index(indexed, index, _) = recv.kind
&& is_range_literal(index)
&& let ty = cx.typeck_results().expr_ty(indexed).peel_refs()
&& (ty.is_str() || is_type_lang_item(cx, ty, LangItem::String))
{
let mut applicability = Applicability::MaybeIncorrect;
let stringish = snippet_with_applicability(cx, indexed.span, "_", &mut applicability);
let range = snippet_with_applicability(cx, index.span, "_", &mut applicability);
span_lint_and_sugg(
cx,
SLICED_STRING_AS_BYTES,
expr.span,
"calling `as_bytes` after slicing a string",
"try",
format!("&{stringish}.as_bytes()[{range}]"),
applicability,
);
}
}
1 change: 1 addition & 0 deletions tests/ui/bytes_nth.fixed
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::sliced_string_as_bytes)]
#![warn(clippy::bytes_nth)]

fn main() {
Expand Down
1 change: 1 addition & 0 deletions tests/ui/bytes_nth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![allow(clippy::unnecessary_operation)]
#![allow(clippy::sliced_string_as_bytes)]
#![warn(clippy::bytes_nth)]

fn main() {
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/bytes_nth.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error: called `.bytes().nth()` on a `String`
--> tests/ui/bytes_nth.rs:6:13
--> tests/ui/bytes_nth.rs:7:13
|
LL | let _ = s.bytes().nth(3);
| ^^^^^^^^^^^^^^^^ help: try: `s.as_bytes().get(3).copied()`
Expand All @@ -8,13 +8,13 @@ LL | let _ = s.bytes().nth(3);
= help: to override `-D warnings` add `#[allow(clippy::bytes_nth)]`

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

error: called `.bytes().nth()` on a `str`
--> tests/ui/bytes_nth.rs:8:13
--> tests/ui/bytes_nth.rs:9:13
|
LL | let _ = s[..].bytes().nth(3);
| ^^^^^^^^^^^^^^^^^^^^ help: try: `s[..].as_bytes().get(3).copied()`
Expand Down
34 changes: 34 additions & 0 deletions tests/ui/sliced_string_as_bytes.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![allow(unused)]
#![warn(clippy::sliced_string_as_bytes)]

use std::ops::{Index, Range};

struct Foo;

struct Bar;

impl Bar {
fn as_bytes(&self) -> &[u8] {
&[0, 1, 2, 3]
}
}

impl Index<Range<usize>> for Foo {
type Output = Bar;

fn index(&self, _: Range<usize>) -> &Self::Output {
&Bar
}
}

fn main() {
let s = "Lorem ipsum";
let string: String = "dolor sit amet".to_owned();

let bytes = &s.as_bytes()[1..5];
let bytes = &string.as_bytes()[1..];
let bytes = &"consectetur adipiscing".as_bytes()[..=5];

let f = Foo;
let bytes = f[0..4].as_bytes();
}
34 changes: 34 additions & 0 deletions tests/ui/sliced_string_as_bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#![allow(unused)]
#![warn(clippy::sliced_string_as_bytes)]

use std::ops::{Index, Range};

struct Foo;

struct Bar;

impl Bar {
fn as_bytes(&self) -> &[u8] {
&[0, 1, 2, 3]
}
}

impl Index<Range<usize>> for Foo {
type Output = Bar;

fn index(&self, _: Range<usize>) -> &Self::Output {
&Bar
}
}

fn main() {
let s = "Lorem ipsum";
let string: String = "dolor sit amet".to_owned();

let bytes = s[1..5].as_bytes();
let bytes = string[1..].as_bytes();
let bytes = "consectetur adipiscing"[..=5].as_bytes();

let f = Foo;
let bytes = f[0..4].as_bytes();
}
23 changes: 23 additions & 0 deletions tests/ui/sliced_string_as_bytes.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
error: calling `as_bytes` after slicing a string
--> tests/ui/sliced_string_as_bytes.rs:28:17
|
LL | let bytes = s[1..5].as_bytes();
| ^^^^^^^^^^^^^^^^^^ help: try: `&s.as_bytes()[1..5]`
|
= note: `-D clippy::sliced-string-as-bytes` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::sliced_string_as_bytes)]`

error: calling `as_bytes` after slicing a string
--> tests/ui/sliced_string_as_bytes.rs:29:17
|
LL | let bytes = string[1..].as_bytes();
| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&string.as_bytes()[1..]`

error: calling `as_bytes` after slicing a string
--> tests/ui/sliced_string_as_bytes.rs:30:17
|
LL | let bytes = "consectetur adipiscing"[..=5].as_bytes();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&"consectetur adipiscing".as_bytes()[..=5]`

error: aborting due to 3 previous errors

Loading