Skip to content

Commit bc27d14

Browse files
Add string_from_utf8_as_bytes linter
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent e298c83 commit bc27d14

File tree

8 files changed

+99
-3
lines changed

8 files changed

+99
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,7 @@ Released 2018-09-13
19521952
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
19531953
[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
19541954
[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
1955+
[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
19551956
[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
19561957
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
19571958
[`struct_excessive_bools`]: https://rust-lang.github.io/rust-clippy/master/index.html#struct_excessive_bools

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
826826
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
827827
&strings::STRING_ADD,
828828
&strings::STRING_ADD_ASSIGN,
829+
&strings::STRING_FROM_UTF8_AS_BYTES,
829830
&strings::STRING_LIT_AS_BYTES,
830831
&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
831832
&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
@@ -1515,6 +1516,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
15151516
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
15161517
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
15171518
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
1519+
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
15181520
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
15191521
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
15201522
LintId::of(&swap::ALMOST_SWAPPED),
@@ -1738,6 +1740,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17381740
LintId::of(&reference::DEREF_ADDROF),
17391741
LintId::of(&reference::REF_IN_DEREF),
17401742
LintId::of(&repeat_once::REPEAT_ONCE),
1743+
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
17411744
LintId::of(&swap::MANUAL_SWAP),
17421745
LintId::of(&temporary_assignment::TEMPORARY_ASSIGNMENT),
17431746
LintId::of(&transmute::CROSSPOINTER_TRANSMUTE),

clippy_lints/src/strings.rs

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_errors::Applicability;
2-
use rustc_hir::{BinOpKind, Expr, ExprKind};
2+
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
33
use rustc_lint::{LateContext, LateLintPass, LintContext};
44
use rustc_middle::lint::in_external_macro;
55
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -8,7 +8,10 @@ use rustc_span::source_map::Spanned;
88
use if_chain::if_chain;
99

1010
use crate::utils::SpanlessEq;
11-
use crate::utils::{get_parent_expr, is_allowed, is_type_diagnostic_item, span_lint, span_lint_and_sugg};
11+
use crate::utils::{
12+
get_parent_expr, is_allowed, is_type_diagnostic_item, match_function_call, method_calls, paths, span_lint,
13+
span_lint_and_sugg,
14+
};
1215

1316
declare_clippy_lint! {
1417
/// **What it does:** Checks for string appends of the form `x = x + y` (without
@@ -173,16 +176,75 @@ fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
173176
}
174177
}
175178

179+
declare_clippy_lint! {
180+
/// **What it does:** Check if the string is transformed to byte array and casted back to string.
181+
///
182+
/// **Why is this bad?** It's unnecessary, the string can be used directly.
183+
///
184+
/// **Known problems:** None
185+
///
186+
/// **Example:**
187+
/// ```rust
188+
/// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
189+
/// ```
190+
/// could be written as
191+
/// ```rust
192+
/// let _ = &"Hello World!"[6..11];
193+
/// ```
194+
pub STRING_FROM_UTF8_AS_BYTES,
195+
complexity,
196+
"casting string slices to byte slices and back"
197+
}
198+
176199
// Max length a b"foo" string can take
177200
const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
178201

179-
declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]);
202+
declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
180203

181204
impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
182205
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
183206
use crate::utils::{snippet, snippet_with_applicability};
184207
use rustc_ast::LitKind;
185208

209+
if_chain! {
210+
// Find std::str::converts::from_utf8
211+
if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
212+
213+
// Find string::as_bytes
214+
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref args) = args[0].kind;
215+
if let ExprKind::Index(ref left, ref right) = args.kind;
216+
let (method_names, expressions, _) = method_calls(left, 1);
217+
if method_names.len() == 1;
218+
if expressions.len() == 1;
219+
if expressions[0].len() == 1;
220+
if method_names[0] == sym!(as_bytes);
221+
222+
// Check for slicer
223+
if let ExprKind::Struct(ref path, _, _) = right.kind;
224+
if let QPath::LangItem(LangItem::Range, _) = path;
225+
226+
then {
227+
let mut applicability = Applicability::MachineApplicable;
228+
let string_expression = &expressions[0][0];
229+
230+
let snippet_app = snippet_with_applicability(
231+
cx,
232+
string_expression.span, "..",
233+
&mut applicability,
234+
);
235+
236+
span_lint_and_sugg(
237+
cx,
238+
STRING_FROM_UTF8_AS_BYTES,
239+
e.span,
240+
"calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
241+
"try",
242+
format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")),
243+
applicability
244+
)
245+
}
246+
}
247+
186248
if_chain! {
187249
if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
188250
if path.ident.name == sym!(as_bytes);

clippy_lints/src/utils/paths.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub const STRING: [&str; 3] = ["alloc", "string", "String"];
120120
pub const STRING_AS_MUT_STR: [&str; 4] = ["alloc", "string", "String", "as_mut_str"];
121121
pub const STRING_AS_STR: [&str; 4] = ["alloc", "string", "String", "as_str"];
122122
pub const STR_ENDS_WITH: [&str; 4] = ["core", "str", "<impl str>", "ends_with"];
123+
pub const STR_FROM_UTF8: [&str; 4] = ["core", "str", "converts", "from_utf8"];
123124
pub const STR_LEN: [&str; 4] = ["core", "str", "<impl str>", "len"];
124125
pub const STR_STARTS_WITH: [&str; 4] = ["core", "str", "<impl str>", "starts_with"];
125126
pub const SYNTAX_CONTEXT: [&str; 3] = ["rustc_span", "hygiene", "SyntaxContext"];

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2230,6 +2230,13 @@ vec![
22302230
deprecation: None,
22312231
module: "methods",
22322232
},
2233+
Lint {
2234+
name: "string_from_utf8_as_bytes",
2235+
group: "complexity",
2236+
desc: "casting string slices to byte slices and back",
2237+
deprecation: None,
2238+
module: "strings",
2239+
},
22332240
Lint {
22342241
name: "string_lit_as_bytes",
22352242
group: "nursery",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// run-rustfix
2+
#![warn(clippy::string_from_utf8_as_bytes)]
3+
4+
fn main() {
5+
let _ = Some(&"Hello World!"[6..11]);
6+
}

tests/ui/string_from_utf8_as_bytes.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// run-rustfix
2+
#![warn(clippy::string_from_utf8_as_bytes)]
3+
4+
fn main() {
5+
let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]);
6+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: calling a slice of `as_bytes()` with `from_utf8` should be not necessary
2+
--> $DIR/string_from_utf8_as_bytes.rs:5:13
3+
|
4+
LL | let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]);
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Some(&"Hello World!"[6..11])`
6+
|
7+
= note: `-D clippy::string-from-utf8-as-bytes` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)