]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/ptr.rs
Fix merge issues.
[rust.git] / clippy_lints / src / ptr.rs
1 //! Checks for usage of  `&Vec[_]` and `&String`.
2
3 use std::borrow::Cow;
4 use rustc::hir::*;
5 use rustc::hir::map::NodeItem;
6 use rustc::lint::*;
7 use rustc::ty;
8 use syntax::ast::NodeId;
9 use syntax::codemap::Span;
10 use syntax_pos::MultiSpan;
11 use utils::{match_qpath, match_type, paths, snippet_opt, span_lint, span_lint_and_then, walk_ptrs_hir_ty};
12 use utils::ptr::get_spans;
13
14 /// **What it does:** This lint checks for function arguments of type `&String`
15 /// or `&Vec` unless the references are mutable. It will also suggest you
16 /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()`
17 /// calls.
18 ///
19 /// **Why is this bad?** Requiring the argument to be of the specific size
20 /// makes the function less useful for no benefit; slices in the form of `&[T]`
21 /// or `&str` usually suffice and can be obtained from other types, too.
22 ///
23 /// **Known problems:** The lint does not follow data. So if you have an
24 /// argument `x` and write `let y = x; y.clone()` the lint will not suggest
25 /// changing that `.clone()` to `.to_owned()`.
26 ///
27 /// Other functions called from this function taking a `&String` or `&Vec`
28 /// argument may also fail to compile if you change the argument. Applying
29 /// this lint on them will fix the problem, but they may be in other crates.
30 ///
31 /// Also there may be `fn(&Vec)`-typed references pointing to your function.
32 /// If you have them, you will get a compiler error after applying this lint's
33 /// suggestions. You then have the choice to undo your changes or change the
34 /// type of the reference.
35 ///
36 /// Note that if the function is part of your public interface, there may be
37 /// other crates referencing it you may not be aware. Carefully deprecate the
38 /// function before applying the lint suggestions in this case.
39 ///
40 /// **Example:**
41 /// ```rust
42 /// fn foo(&Vec<u32>) { .. }
43 /// ```
44 declare_lint! {
45     pub PTR_ARG,
46     Warn,
47     "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` \
48      instead, respectively"
49 }
50
51 /// **What it does:** This lint checks for equality comparisons with `ptr::null`
52 ///
53 /// **Why is this bad?** It's easier and more readable to use the inherent
54 /// `.is_null()`
55 /// method instead
56 ///
57 /// **Known problems:** None.
58 ///
59 /// **Example:**
60 /// ```rust
61 /// if x == ptr::null { .. }
62 /// ```
63 declare_lint! {
64     pub CMP_NULL,
65     Warn,
66     "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead."
67 }
68
69 /// **What it does:** This lint checks for functions that take immutable
70 /// references and return
71 /// mutable ones.
72 ///
73 /// **Why is this bad?** This is trivially unsound, as one can create two
74 /// mutable references
75 /// from the same (immutable!) source. This
76 /// [error](https://github.com/rust-lang/rust/issues/39465)
77 /// actually lead to an interim Rust release 1.15.1.
78 ///
79 /// **Known problems:** To be on the conservative side, if there's at least one
80 /// mutable reference
81 /// with the output lifetime, this lint will not trigger. In practice, this
82 /// case is unlikely anyway.
83 ///
84 /// **Example:**
85 /// ```rust
86 /// fn foo(&Foo) -> &mut Bar { .. }
87 /// ```
88 declare_lint! {
89     pub MUT_FROM_REF,
90     Warn,
91     "fns that create mutable refs from immutable ref args"
92 }
93
94 #[derive(Copy, Clone)]
95 pub struct PointerPass;
96
97 impl LintPass for PointerPass {
98     fn get_lints(&self) -> LintArray {
99         lint_array!(PTR_ARG, CMP_NULL, MUT_FROM_REF)
100     }
101 }
102
103 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PointerPass {
104     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx Item) {
105         if let ItemFn(ref decl, _, _, _, _, body_id) = item.node {
106             check_fn(cx, decl, item.id, Some(body_id));
107         }
108     }
109
110     fn check_impl_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx ImplItem) {
111         if let ImplItemKind::Method(ref sig, body_id) = item.node {
112             if let Some(NodeItem(it)) = cx.tcx.hir.find(cx.tcx.hir.get_parent(item.id)) {
113                 if let ItemImpl(_, _, _, _, Some(_), _, _) = it.node {
114                     return; // ignore trait impls
115                 }
116             }
117             check_fn(cx, &sig.decl, item.id, Some(body_id));
118         }
119     }
120
121     fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx TraitItem) {
122         if let TraitItemKind::Method(ref sig, ref trait_method) = item.node {
123             let body_id = if let TraitMethod::Provided(b) = *trait_method {
124                 Some(b)
125             } else {
126                 None
127             };
128             check_fn(cx, &sig.decl, item.id, body_id);
129         }
130     }
131
132     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
133         if let ExprBinary(ref op, ref l, ref r) = expr.node {
134             if (op.node == BiEq || op.node == BiNe) && (is_null_path(l) || is_null_path(r)) {
135                 span_lint(
136                     cx,
137                     CMP_NULL,
138                     expr.span,
139                     "Comparing with null is better expressed by the .is_null() method",
140                 );
141             }
142         }
143     }
144 }
145
146 fn check_fn(cx: &LateContext, decl: &FnDecl, fn_id: NodeId, opt_body_id: Option<BodyId>) {
147     let fn_def_id = cx.tcx.hir.local_def_id(fn_id);
148     let sig = cx.tcx.fn_sig(fn_def_id);
149     let fn_ty = sig.skip_binder();
150
151     for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() {
152         if let ty::TyRef(
153             _,
154             ty::TypeAndMut {
155                 ty,
156                 mutbl: MutImmutable,
157             },
158         ) = ty.sty
159         {
160             if match_type(cx, ty, &paths::VEC) {
161                 let mut ty_snippet = None;
162                 if_chain! {
163                     if let TyPath(QPath::Resolved(_, ref path)) = walk_ptrs_hir_ty(arg).node;
164                     if let Some(&PathSegment{parameters: Some(ref parameters), ..}) = path.segments.last();
165                     if parameters.types.len() == 1;
166                     then {
167                         ty_snippet = snippet_opt(cx, parameters.types[0].span);
168                     }
169                 };
170                 if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
171                     span_lint_and_then(
172                         cx,
173                         PTR_ARG,
174                         arg.span,
175                         "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \
176                          with non-Vec-based slices.",
177                         |db| {
178                             if let Some(ref snippet) = ty_snippet {
179                                 db.span_suggestion(arg.span, "change this to", format!("&[{}]", snippet));
180                             }
181                             for (clonespan, suggestion) in spans {
182                                 db.span_suggestion(
183                                     clonespan,
184                                     &snippet_opt(cx, clonespan).map_or(
185                                         "change the call to".into(),
186                                         |x| Cow::Owned(format!("change `{}` to", x)),
187                                     ),
188                                     suggestion.into(),
189                                 );
190                             }
191                         },
192                     );
193                 }
194             } else if match_type(cx, ty, &paths::STRING) {
195                 if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
196                     span_lint_and_then(
197                         cx,
198                         PTR_ARG,
199                         arg.span,
200                         "writing `&String` instead of `&str` involves a new object where a slice will do.",
201                         |db| {
202                             db.span_suggestion(arg.span, "change this to", "&str".into());
203                             for (clonespan, suggestion) in spans {
204                                 db.span_suggestion_short(
205                                     clonespan,
206                                     &snippet_opt(cx, clonespan).map_or(
207                                         "change the call to".into(),
208                                         |x| Cow::Owned(format!("change `{}` to", x)),
209                                     ),
210                                     suggestion.into(),
211                                 );
212                             }
213                         },
214                     );
215                 }
216             }
217         }
218     }
219
220     if let FunctionRetTy::Return(ref ty) = decl.output {
221         if let Some((out, MutMutable, _)) = get_rptr_lm(ty) {
222             let mut immutables = vec![];
223             for (_, ref mutbl, ref argspan) in decl.inputs
224                 .iter()
225                 .filter_map(|ty| get_rptr_lm(ty))
226                 .filter(|&(lt, _, _)| lt.name == out.name)
227             {
228                 if *mutbl == MutMutable {
229                     return;
230                 }
231                 immutables.push(*argspan);
232             }
233             if immutables.is_empty() {
234                 return;
235             }
236             span_lint_and_then(cx, MUT_FROM_REF, ty.span, "mutable borrow from immutable input(s)", |db| {
237                 let ms = MultiSpan::from_spans(immutables);
238                 db.span_note(ms, "immutable borrow here");
239             });
240         }
241     }
242 }
243
244 fn get_rptr_lm(ty: &Ty) -> Option<(&Lifetime, Mutability, Span)> {
245     if let Ty_::TyRptr(ref lt, ref m) = ty.node {
246         Some((lt, m.mutbl, ty.span))
247     } else {
248         None
249     }
250 }
251
252 fn is_null_path(expr: &Expr) -> bool {
253     if let ExprCall(ref pathexp, ref args) = expr.node {
254         if args.is_empty() {
255             if let ExprPath(ref path) = pathexp.node {
256                 return match_qpath(path, &paths::PTR_NULL) || match_qpath(path, &paths::PTR_NULL_MUT);
257             }
258         }
259     }
260     false
261 }