diff options
| author | wowinter13 <vla-dy@yandex.ru> | 2025-01-15 15:58:18 +0100 |
|---|---|---|
| committer | wowinter13 <vla-dy@yandex.ru> | 2025-01-25 18:42:43 +0100 |
| commit | d8752dbf4080c5ce4b87ae753a0ef510ff67617b (patch) | |
| tree | e989811a35121db2ed81f3696e656643fe97d146 | |
| parent | 4825666bd359c429c7e218e318a36299c161de58 (diff) | |
| download | rust-d8752dbf4080c5ce4b87ae753a0ef510ff67617b.tar.gz rust-d8752dbf4080c5ce4b87ae753a0ef510ff67617b.zip | |
New lint
| -rw-r--r-- | clippy_lints/src/declared_lints.rs | 1 | ||||
| -rw-r--r-- | clippy_lints/src/methods/mod.rs | 31 | ||||
| -rw-r--r-- | clippy_lints/src/methods/slice_as_bytes.rs | 30 | ||||
| -rw-r--r-- | tests/ui/bytes_nth.fixed | 1 | ||||
| -rw-r--r-- | tests/ui/bytes_nth.rs | 1 | ||||
| -rw-r--r-- | tests/ui/bytes_nth.stderr | 6 | ||||
| -rw-r--r-- | tests/ui/slice_as_bytes.fixed | 35 | ||||
| -rw-r--r-- | tests/ui/slice_as_bytes.rs | 36 | ||||
| -rw-r--r-- | tests/ui/slice_as_bytes.stderr | 21 |
9 files changed, 159 insertions, 3 deletions
diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 86410c16d95..0917dcff308 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -503,6 +503,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::WAKER_CLONE_WAKE_INFO, crate::methods::WRONG_SELF_CONVENTION_INFO, crate::methods::ZST_OFFSET_INFO, + crate::methods::SLICE_AS_BYTES_INFO, crate::min_ident_chars::MIN_IDENT_CHARS_INFO, crate::minmax::MIN_MAX_INFO, crate::misc::SHORT_CIRCUIT_STATEMENT_INFO, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9bfa5947990..4a00da39a4b 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -102,6 +102,7 @@ mod single_char_add_str; mod single_char_insert_string; mod single_char_push_string; mod skip_while_next; +mod slice_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.72.0"] + pub SLICE_AS_BYTES, + pedantic, + "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, + SLICE_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); } + slice_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), diff --git a/clippy_lints/src/methods/slice_as_bytes.rs b/clippy_lints/src/methods/slice_as_bytes.rs new file mode 100644 index 00000000000..6c2aeab3308 --- /dev/null +++ b/clippy_lints/src/methods/slice_as_bytes.rs @@ -0,0 +1,30 @@ +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::SLICE_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(); + if 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, + SLICE_AS_BYTES, + expr.span, + "calling `as_bytes` after slicing a string", + "try", + format!("&{stringish}.as_bytes()[{range}]"), + applicability, + ); + } + } +} diff --git a/tests/ui/bytes_nth.fixed b/tests/ui/bytes_nth.fixed index 11deb2390fd..d58eb5227fb 100644 --- a/tests/ui/bytes_nth.fixed +++ b/tests/ui/bytes_nth.fixed @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_operation)] +#![allow(clippy::slice_as_bytes)] #![warn(clippy::bytes_nth)] fn main() { diff --git a/tests/ui/bytes_nth.rs b/tests/ui/bytes_nth.rs index 62d9c7a5ea7..bbfe388e8bb 100644 --- a/tests/ui/bytes_nth.rs +++ b/tests/ui/bytes_nth.rs @@ -1,4 +1,5 @@ #![allow(clippy::unnecessary_operation)] +#![allow(clippy::slice_as_bytes)] #![warn(clippy::bytes_nth)] fn main() { diff --git a/tests/ui/bytes_nth.stderr b/tests/ui/bytes_nth.stderr index c6f21576c3d..c5f341cb37f 100644 --- a/tests/ui/bytes_nth.stderr +++ b/tests/ui/bytes_nth.stderr @@ -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()` diff --git a/tests/ui/slice_as_bytes.fixed b/tests/ui/slice_as_bytes.fixed new file mode 100644 index 00000000000..fcbcb80a963 --- /dev/null +++ b/tests/ui/slice_as_bytes.fixed @@ -0,0 +1,35 @@ +//@run-rustfix +#![allow(unused)] +#![warn(clippy::slice_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(); +} \ No newline at end of file diff --git a/tests/ui/slice_as_bytes.rs b/tests/ui/slice_as_bytes.rs new file mode 100644 index 00000000000..4ff6c5ac784 --- /dev/null +++ b/tests/ui/slice_as_bytes.rs @@ -0,0 +1,36 @@ +//@run-rustfix + +#![allow(unused)] +#![warn(clippy::slice_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(); +} \ No newline at end of file diff --git a/tests/ui/slice_as_bytes.stderr b/tests/ui/slice_as_bytes.stderr new file mode 100644 index 00000000000..2a3251b4d9c --- /dev/null +++ b/tests/ui/slice_as_bytes.stderr @@ -0,0 +1,21 @@ +error: slicing a str before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking +--> $DIR/slice_as_bytes.rs:29:17 +| +LL | let bytes = s[1..5].as_bytes(); +| ^^^^^^^^^^^^^^^^^^ help: try: `&s.as_bytes()[1..5]` +| += note: `-D clippy::slice-as-bytes` implied by `-D warnings` + +error: slicing a String before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking +--> $DIR/slice_as_bytes.rs:30:17 +| +LL | let bytes = string[1..].as_bytes(); +| ^^^^^^^^^^^^^^^^^^^^^^ help: try: `&string.as_bytes()[1..]` + +error: slicing a str before calling `as_bytes` results in needless UTF-8 alignment checks, and has the possiblity of panicking +--> $DIR/slice_as_bytes.rs:31:17 +| +LL | let bytes = "consectetur adipiscing"[..=5].as_bytes(); +| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `&"consectetur adipiscing".as_bytes()[..=5]` + +error: aborting due to 3 previous errors |
