Skip to content

Commit 4177a96

Browse files
Add string_from_utf8_as_bytes linter
Signed-off-by: Patrick José Pereira <[email protected]>
1 parent 9408c68 commit 4177a96

File tree

7 files changed

+85
-3
lines changed

7 files changed

+85
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1824,6 +1824,7 @@ Released 2018-09-13
18241824
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
18251825
[`string_add_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add_assign
18261826
[`string_extend_chars`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_extend_chars
1827+
[`string_from_utf8_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_from_utf8_as_bytes
18271828
[`string_lit_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_lit_as_bytes
18281829
[`string_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_to_string
18291830
[`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
@@ -803,6 +803,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
803803
&stable_sort_primitive::STABLE_SORT_PRIMITIVE,
804804
&strings::STRING_ADD,
805805
&strings::STRING_ADD_ASSIGN,
806+
&strings::STRING_FROM_UTF8_AS_BYTES,
806807
&strings::STRING_LIT_AS_BYTES,
807808
&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL,
808809
&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL,
@@ -1475,6 +1476,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14751476
LintId::of(&single_component_path_imports::SINGLE_COMPONENT_PATH_IMPORTS),
14761477
LintId::of(&slow_vector_initialization::SLOW_VECTOR_INITIALIZATION),
14771478
LintId::of(&stable_sort_primitive::STABLE_SORT_PRIMITIVE),
1479+
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
14781480
LintId::of(&strings::STRING_LIT_AS_BYTES),
14791481
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
14801482
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
@@ -1769,6 +1771,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
17691771
LintId::of(&regex::INVALID_REGEX),
17701772
LintId::of(&self_assignment::SELF_ASSIGNMENT),
17711773
LintId::of(&serde_api::SERDE_API_MISUSE),
1774+
LintId::of(&strings::STRING_FROM_UTF8_AS_BYTES),
17721775
LintId::of(&suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL),
17731776
LintId::of(&suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL),
17741777
LintId::of(&swap::ALMOST_SWAPPED),

clippy_lints/src/strings.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
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};
66
use rustc_span::source_map::Spanned;
7+
use rustc_span::symbol::SymbolStr;
78

89
use if_chain::if_chain;
910

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

1317
declare_clippy_lint! {
1418
/// **What it does:** Checks for string appends of the form `x = x + y` (without
@@ -153,16 +157,65 @@ fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
153157
}
154158
}
155159

160+
declare_clippy_lint! {
161+
/// **What it does:** Check if the string is transformed to byte array and casted back to string.
162+
///
163+
/// **Why is this bad?** It's unnecessary, the string can be used directly.
164+
///
165+
/// **Known problems:** None
166+
///
167+
/// **Example:**
168+
/// ```rust
169+
/// let string = "Hello World!";
170+
/// let string2 = std::str::from_utf8(&string.as_bytes()[6..11]).unwrap();
171+
/// ```
172+
/// could be written as
173+
/// ```rust
174+
/// let string = "Hello World!";
175+
/// let string2 = &string[6..1];
176+
/// ```
177+
pub STRING_FROM_UTF8_AS_BYTES,
178+
correctness,
179+
"make sure that access from slice should be done from bytes and not from string directly"
180+
}
181+
156182
// Max length a b"foo" string can take
157183
const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
158184

159-
declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES]);
185+
declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
160186

161187
impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
162188
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
163189
use crate::utils::{snippet, snippet_with_applicability};
164190
use rustc_ast::LitKind;
165191

192+
if_chain! {
193+
// Find std::str::converts::from_utf8
194+
if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
195+
196+
// Find string::as_bytes
197+
if let ExprKind::AddrOf(BorrowKind::Ref, _, ref args) = args[0].kind;
198+
if let ExprKind::Index(ref left, ref right) = args.kind;
199+
let (method_names, _, _) = method_calls(left, 1);
200+
let method_names: Vec<SymbolStr> = method_names.iter().map(|s| s.as_str()).collect();
201+
let method_names: Vec<&str> = method_names.iter().map(|s| &**s).collect();
202+
if method_names.len() == 1;
203+
if method_names[0] == "as_bytes";
204+
205+
// Check for slicer
206+
if let ExprKind::Struct(ref path, _, _) = right.kind;
207+
if let QPath::LangItem(LangItem::Range, _) = path;
208+
209+
then {
210+
span_lint(
211+
cx,
212+
STRING_FROM_UTF8_AS_BYTES,
213+
e.span,
214+
"calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
215+
);
216+
}
217+
}
218+
166219
if_chain! {
167220
if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
168221
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
@@ -2166,6 +2166,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
21662166
deprecation: None,
21672167
module: "methods",
21682168
},
2169+
Lint {
2170+
name: "string_from_utf8_as_bytes",
2171+
group: "correctness",
2172+
desc: "make sure that access from slice should be done from bytes and not from string directly",
2173+
deprecation: None,
2174+
module: "strings",
2175+
},
21692176
Lint {
21702177
name: "string_lit_as_bytes",
21712178
group: "style",

tests/ui/string_from_utf8_as_bytes.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#![warn(clippy::string_from_utf8_as_bytes)]
2+
use std::str;
3+
4+
fn main() {
5+
let string = "Hello World!";
6+
let string2 = str::from_utf8(&string.as_bytes()[6..11]).unwrap();
7+
}
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:6:19
3+
|
4+
LL | let string2 = str::from_utf8(&string.as_bytes()[6..11]).unwrap();
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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)