]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/strings.rs
Auto merge of #6304 - matthiaskrgr:crash_6302, r=llogiq
[rust.git] / clippy_lints / src / strings.rs
1 use rustc_errors::Applicability;
2 use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, LangItem, QPath};
3 use rustc_lint::{LateContext, LateLintPass, LintContext};
4 use rustc_middle::lint::in_external_macro;
5 use rustc_session::{declare_lint_pass, declare_tool_lint};
6 use rustc_span::source_map::Spanned;
7 use rustc_span::sym;
8
9 use if_chain::if_chain;
10
11 use crate::utils::SpanlessEq;
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 };
16
17 declare_clippy_lint! {
18     /// **What it does:** Checks for string appends of the form `x = x + y` (without
19     /// `let`!).
20     ///
21     /// **Why is this bad?** It's not really bad, but some people think that the
22     /// `.push_str(_)` method is more readable.
23     ///
24     /// **Known problems:** None.
25     ///
26     /// **Example:**
27     ///
28     /// ```rust
29     /// let mut x = "Hello".to_owned();
30     /// x = x + ", World";
31     ///
32     /// // More readable
33     /// x += ", World";
34     /// x.push_str(", World");
35     /// ```
36     pub STRING_ADD_ASSIGN,
37     pedantic,
38     "using `x = x + ..` where x is a `String` instead of `push_str()`"
39 }
40
41 declare_clippy_lint! {
42     /// **What it does:** Checks for all instances of `x + _` where `x` is of type
43     /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not*
44     /// match.
45     ///
46     /// **Why is this bad?** It's not bad in and of itself. However, this particular
47     /// `Add` implementation is asymmetric (the other operand need not be `String`,
48     /// but `x` does), while addition as mathematically defined is symmetric, also
49     /// the `String::push_str(_)` function is a perfectly good replacement.
50     /// Therefore, some dislike it and wish not to have it in their code.
51     ///
52     /// That said, other people think that string addition, having a long tradition
53     /// in other languages is actually fine, which is why we decided to make this
54     /// particular lint `allow` by default.
55     ///
56     /// **Known problems:** None.
57     ///
58     /// **Example:**
59     ///
60     /// ```rust
61     /// let x = "Hello".to_owned();
62     /// x + ", World";
63     /// ```
64     pub STRING_ADD,
65     restriction,
66     "using `x + ..` where x is a `String` instead of `push_str()`"
67 }
68
69 declare_clippy_lint! {
70     /// **What it does:** Checks for the `as_bytes` method called on string literals
71     /// that contain only ASCII characters.
72     ///
73     /// **Why is this bad?** Byte string literals (e.g., `b"foo"`) can be used
74     /// instead. They are shorter but less discoverable than `as_bytes()`.
75     ///
76     /// **Known Problems:**
77     /// `"str".as_bytes()` and the suggested replacement of `b"str"` are not
78     /// equivalent because they have different types. The former is `&[u8]`
79     /// while the latter is `&[u8; 3]`. That means in general they will have a
80     /// different set of methods and different trait implementations.
81     ///
82     /// ```compile_fail
83     /// fn f(v: Vec<u8>) {}
84     ///
85     /// f("...".as_bytes().to_owned()); // works
86     /// f(b"...".to_owned()); // does not work, because arg is [u8; 3] not Vec<u8>
87     ///
88     /// fn g(r: impl std::io::Read) {}
89     ///
90     /// g("...".as_bytes()); // works
91     /// g(b"..."); // does not work
92     /// ```
93     ///
94     /// The actual equivalent of `"str".as_bytes()` with the same type is not
95     /// `b"str"` but `&b"str"[..]`, which is a great deal of punctuation and not
96     /// more readable than a function call.
97     ///
98     /// **Example:**
99     /// ```rust
100     /// // Bad
101     /// let bs = "a byte string".as_bytes();
102     ///
103     /// // Good
104     /// let bs = b"a byte string";
105     /// ```
106     pub STRING_LIT_AS_BYTES,
107     nursery,
108     "calling `as_bytes` on a string literal instead of using a byte string literal"
109 }
110
111 declare_lint_pass!(StringAdd => [STRING_ADD, STRING_ADD_ASSIGN]);
112
113 impl<'tcx> LateLintPass<'tcx> for StringAdd {
114     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
115         if in_external_macro(cx.sess(), e.span) {
116             return;
117         }
118
119         if let ExprKind::Binary(
120             Spanned {
121                 node: BinOpKind::Add, ..
122             },
123             ref left,
124             _,
125         ) = e.kind
126         {
127             if is_string(cx, left) {
128                 if !is_allowed(cx, STRING_ADD_ASSIGN, e.hir_id) {
129                     let parent = get_parent_expr(cx, e);
130                     if let Some(p) = parent {
131                         if let ExprKind::Assign(ref target, _, _) = p.kind {
132                             // avoid duplicate matches
133                             if SpanlessEq::new(cx).eq_expr(target, left) {
134                                 return;
135                             }
136                         }
137                     }
138                 }
139                 span_lint(
140                     cx,
141                     STRING_ADD,
142                     e.span,
143                     "you added something to a string. Consider using `String::push_str()` instead",
144                 );
145             }
146         } else if let ExprKind::Assign(ref target, ref src, _) = e.kind {
147             if is_string(cx, target) && is_add(cx, src, target) {
148                 span_lint(
149                     cx,
150                     STRING_ADD_ASSIGN,
151                     e.span,
152                     "you assigned the result of adding something to this string. Consider using \
153                      `String::push_str()` instead",
154                 );
155             }
156         }
157     }
158 }
159
160 fn is_string(cx: &LateContext<'_>, e: &Expr<'_>) -> bool {
161     is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(e).peel_refs(), sym::string_type)
162 }
163
164 fn is_add(cx: &LateContext<'_>, src: &Expr<'_>, target: &Expr<'_>) -> bool {
165     match src.kind {
166         ExprKind::Binary(
167             Spanned {
168                 node: BinOpKind::Add, ..
169             },
170             ref left,
171             _,
172         ) => SpanlessEq::new(cx).eq_expr(target, left),
173         ExprKind::Block(ref block, _) => {
174             block.stmts.is_empty() && block.expr.as_ref().map_or(false, |expr| is_add(cx, expr, target))
175         },
176         _ => false,
177     }
178 }
179
180 declare_clippy_lint! {
181     /// **What it does:** Check if the string is transformed to byte array and casted back to string.
182     ///
183     /// **Why is this bad?** It's unnecessary, the string can be used directly.
184     ///
185     /// **Known problems:** None
186     ///
187     /// **Example:**
188     /// ```rust
189     /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap();
190     /// ```
191     /// could be written as
192     /// ```rust
193     /// let _ = &"Hello World!"[6..11];
194     /// ```
195     pub STRING_FROM_UTF8_AS_BYTES,
196     complexity,
197     "casting string slices to byte slices and back"
198 }
199
200 // Max length a b"foo" string can take
201 const MAX_LENGTH_BYTE_STRING_LIT: usize = 32;
202
203 declare_lint_pass!(StringLitAsBytes => [STRING_LIT_AS_BYTES, STRING_FROM_UTF8_AS_BYTES]);
204
205 impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes {
206     fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
207         use crate::utils::{snippet, snippet_with_applicability};
208         use rustc_ast::LitKind;
209
210         if_chain! {
211             // Find std::str::converts::from_utf8
212             if let Some(args) = match_function_call(cx, e, &paths::STR_FROM_UTF8);
213
214             // Find string::as_bytes
215             if let ExprKind::AddrOf(BorrowKind::Ref, _, ref args) = args[0].kind;
216             if let ExprKind::Index(ref left, ref right) = args.kind;
217             let (method_names, expressions, _) = method_calls(left, 1);
218             if method_names.len() == 1;
219             if expressions.len() == 1;
220             if expressions[0].len() == 1;
221             if method_names[0] == sym!(as_bytes);
222
223             // Check for slicer
224             if let ExprKind::Struct(ref path, _, _) = right.kind;
225             if let QPath::LangItem(LangItem::Range, _) = path;
226
227             then {
228                 let mut applicability = Applicability::MachineApplicable;
229                 let string_expression = &expressions[0][0];
230
231                 let snippet_app = snippet_with_applicability(
232                     cx,
233                     string_expression.span, "..",
234                     &mut applicability,
235                 );
236
237                 span_lint_and_sugg(
238                     cx,
239                     STRING_FROM_UTF8_AS_BYTES,
240                     e.span,
241                     "calling a slice of `as_bytes()` with `from_utf8` should be not necessary",
242                     "try",
243                     format!("Some(&{}[{}])", snippet_app, snippet(cx, right.span, "..")),
244                     applicability
245                 )
246             }
247         }
248
249         if_chain! {
250             if let ExprKind::MethodCall(path, _, args, _) = &e.kind;
251             if path.ident.name == sym!(as_bytes);
252             if let ExprKind::Lit(lit) = &args[0].kind;
253             if let LitKind::Str(lit_content, _) = &lit.node;
254             then {
255                 let callsite = snippet(cx, args[0].span.source_callsite(), r#""foo""#);
256                 let mut applicability = Applicability::MachineApplicable;
257                 if callsite.starts_with("include_str!") {
258                     span_lint_and_sugg(
259                         cx,
260                         STRING_LIT_AS_BYTES,
261                         e.span,
262                         "calling `as_bytes()` on `include_str!(..)`",
263                         "consider using `include_bytes!(..)` instead",
264                         snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability).replacen(
265                             "include_str",
266                             "include_bytes",
267                             1,
268                         ),
269                         applicability,
270                     );
271                 } else if lit_content.as_str().is_ascii()
272                     && lit_content.as_str().len() <= MAX_LENGTH_BYTE_STRING_LIT
273                     && !args[0].span.from_expansion()
274                 {
275                     span_lint_and_sugg(
276                         cx,
277                         STRING_LIT_AS_BYTES,
278                         e.span,
279                         "calling `as_bytes()` on a string literal",
280                         "consider using a byte string literal instead",
281                         format!(
282                             "b{}",
283                             snippet_with_applicability(cx, args[0].span, r#""foo""#, &mut applicability)
284                         ),
285                         applicability,
286                     );
287                 }
288             }
289         }
290     }
291 }