New lint sliced_string_as_bytes (#14002)
resurrection of https://github.com/rust-lang/rust-clippy/pull/10984 fixes https://github.com/rust-lang/rust-clippy/issues/10981 changelog: [`sliced_string_as_bytes`]: add new lint `sliced_string_as_bytes`
This commit is contained in:
commit
85bbba69b0
10 changed files with 158 additions and 3 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
@ -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.
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
29
clippy_lints/src/methods/sliced_string_as_bytes.rs
Normal file
29
clippy_lints/src/methods/sliced_string_as_bytes.rs
Normal file
|
|
@ -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,4 +1,5 @@
|
|||
#![allow(clippy::unnecessary_operation)]
|
||||
#![allow(clippy::sliced_string_as_bytes)]
|
||||
#![warn(clippy::bytes_nth)]
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::unnecessary_operation)]
|
||||
#![allow(clippy::sliced_string_as_bytes)]
|
||||
#![warn(clippy::bytes_nth)]
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -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()`
|
||||
|
|
@ -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()`
|
||||
|
|
|
|||
34
tests/ui/sliced_string_as_bytes.fixed
Normal file
34
tests/ui/sliced_string_as_bytes.fixed
Normal file
|
|
@ -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
tests/ui/sliced_string_as_bytes.rs
Normal file
34
tests/ui/sliced_string_as_bytes.rs
Normal file
|
|
@ -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
tests/ui/sliced_string_as_bytes.stderr
Normal file
23
tests/ui/sliced_string_as_bytes.stderr
Normal file
|
|
@ -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…
Add table
Add a link
Reference in a new issue