about summary refs log tree commit diff
diff options
context:
space:
mode:
authorwowinter13 <vla-dy@yandex.ru>2025-01-15 15:58:18 +0100
committerwowinter13 <vla-dy@yandex.ru>2025-01-25 18:42:43 +0100
commitd8752dbf4080c5ce4b87ae753a0ef510ff67617b (patch)
treee989811a35121db2ed81f3696e656643fe97d146
parent4825666bd359c429c7e218e318a36299c161de58 (diff)
downloadrust-d8752dbf4080c5ce4b87ae753a0ef510ff67617b.tar.gz
rust-d8752dbf4080c5ce4b87ae753a0ef510ff67617b.zip
New lint
-rw-r--r--clippy_lints/src/declared_lints.rs1
-rw-r--r--clippy_lints/src/methods/mod.rs31
-rw-r--r--clippy_lints/src/methods/slice_as_bytes.rs30
-rw-r--r--tests/ui/bytes_nth.fixed1
-rw-r--r--tests/ui/bytes_nth.rs1
-rw-r--r--tests/ui/bytes_nth.stderr6
-rw-r--r--tests/ui/slice_as_bytes.fixed35
-rw-r--r--tests/ui/slice_as_bytes.rs36
-rw-r--r--tests/ui/slice_as_bytes.stderr21
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