1 //! Checks for usage of `&Vec[_]` and `&String`.
3 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
4 use clippy_utils::source::snippet_opt;
5 use clippy_utils::ty::expr_sig;
6 use clippy_utils::visitors::contains_unsafe_block;
7 use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
8 use if_chain::if_chain;
9 use rustc_errors::Applicability;
10 use rustc_hir::def_id::DefId;
11 use rustc_hir::hir_id::HirIdMap;
12 use rustc_hir::intravisit::{walk_expr, Visitor};
14 self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg,
15 ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
16 TraitItem, TraitItemKind, TyKind, Unsafety,
18 use rustc_lint::{LateContext, LateLintPass};
19 use rustc_middle::hir::nested_filter;
20 use rustc_middle::ty::{self, AssocItems, AssocKind, Ty};
21 use rustc_session::{declare_lint_pass, declare_tool_lint};
22 use rustc_span::source_map::Span;
23 use rustc_span::symbol::Symbol;
24 use rustc_span::{sym, MultiSpan};
28 declare_clippy_lint! {
30 /// This lint checks for function arguments of type `&String`, `&Vec`,
31 /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
32 /// with the appropriate `.to_owned()`/`to_string()` calls.
34 /// ### Why is this bad?
35 /// Requiring the argument to be of the specific size
36 /// makes the function less useful for no benefit; slices in the form of `&[T]`
37 /// or `&str` usually suffice and can be obtained from other types, too.
39 /// ### Known problems
40 /// There may be `fn(&Vec)`-typed references pointing to your function.
41 /// If you have them, you will get a compiler error after applying this lint's
42 /// suggestions. You then have the choice to undo your changes or change the
43 /// type of the reference.
45 /// Note that if the function is part of your public interface, there may be
46 /// other crates referencing it, of which you may not be aware. Carefully
47 /// deprecate the function before applying the lint suggestions in this case.
52 /// fn foo(&Vec<u32>) { .. }
55 /// fn foo(&[u32]) { .. }
57 #[clippy::version = "pre 1.29.0"]
60 "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
63 declare_clippy_lint! {
65 /// This lint checks for equality comparisons with `ptr::null`
67 /// ### Why is this bad?
68 /// It's easier and more readable to use the inherent
75 /// if x == ptr::null {
84 #[clippy::version = "pre 1.29.0"]
87 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
90 declare_clippy_lint! {
92 /// This lint checks for functions that take immutable
93 /// references and return mutable ones.
95 /// ### Why is this bad?
96 /// This is trivially unsound, as one can create two
97 /// mutable references from the same (immutable!) source.
98 /// This [error](https://github.com/rust-lang/rust/issues/39465)
99 /// actually lead to an interim Rust release 1.15.1.
101 /// ### Known problems
102 /// To be on the conservative side, if there's at least one
103 /// mutable reference with the output lifetime, this lint will not trigger.
104 /// In practice, this case is unlikely anyway.
108 /// fn foo(&Foo) -> &mut Bar { .. }
110 #[clippy::version = "pre 1.29.0"]
113 "fns that create mutable refs from immutable ref args"
116 declare_clippy_lint! {
118 /// This lint checks for invalid usages of `ptr::null`.
120 /// ### Why is this bad?
121 /// This causes undefined behavior.
125 /// // Bad. Undefined behavior
126 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
131 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
133 #[clippy::version = "1.53.0"]
134 pub INVALID_NULL_PTR_USAGE,
136 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
139 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
141 impl<'tcx> LateLintPass<'tcx> for Ptr {
142 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
143 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
144 if matches!(trait_method, TraitFn::Provided(_)) {
145 // Handled by check body.
149 check_mut_from_ref(cx, sig, None);
150 for arg in check_fn_args(
152 cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
156 .filter(|arg| arg.mutability() == Mutability::Not)
164 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
165 Applicability::Unspecified,
171 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
172 let hir = cx.tcx.hir();
173 let mut parents = hir.parent_iter(body.value.hir_id);
174 let (item_id, sig, is_trait_item) = match parents.next() {
175 Some((_, Node::Item(i))) => {
176 if let ItemKind::Fn(sig, ..) = &i.kind {
177 (i.def_id, sig, false)
182 Some((_, Node::ImplItem(i))) => {
183 if !matches!(parents.next(),
184 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
188 if let ImplItemKind::Fn(sig, _) = &i.kind {
189 (i.def_id, sig, false)
194 Some((_, Node::TraitItem(i))) => {
195 if let TraitItemKind::Fn(sig, _) = &i.kind {
196 (i.def_id, sig, true)
204 check_mut_from_ref(cx, sig, Some(body));
206 let sig = cx.tcx.fn_sig(item_id).skip_binder();
207 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
208 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
210 let results = check_ptr_arg_usage(cx, body, &lint_args);
212 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
213 span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
214 diag.multipart_suggestion(
216 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
217 .chain(result.replacements.iter().map(|r| {
220 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
224 Applicability::Unspecified,
230 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
231 if let ExprKind::Binary(ref op, l, r) = expr.kind {
232 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
237 "comparing with null is better expressed by the `.is_null()` method",
241 check_invalid_ptr_usage(cx, expr);
246 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
247 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
248 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
249 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
250 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
251 (&paths::PTR_COPY, &[0, 1]),
252 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
253 (&paths::PTR_READ, &[0]),
254 (&paths::PTR_READ_UNALIGNED, &[0]),
255 (&paths::PTR_READ_VOLATILE, &[0]),
256 (&paths::PTR_REPLACE, &[0]),
257 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
258 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
259 (&paths::PTR_SWAP, &[0, 1]),
260 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
261 (&paths::PTR_WRITE, &[0]),
262 (&paths::PTR_WRITE_UNALIGNED, &[0]),
263 (&paths::PTR_WRITE_VOLATILE, &[0]),
264 (&paths::PTR_WRITE_BYTES, &[0]),
268 if let ExprKind::Call(fun, args) = expr.kind;
269 if let ExprKind::Path(ref qpath) = fun.kind;
270 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
271 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
272 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
274 .find(|&&(fn_path, _)| fn_path == fun_def_path);
276 for &arg_idx in arg_indices {
277 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
280 INVALID_NULL_PTR_USAGE,
282 "pointer must be non-null",
284 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
285 Applicability::MachineApplicable,
294 struct PtrArgResult {
296 replacements: Vec<PtrArgReplacement>,
299 struct PtrArgReplacement {
302 replacement: &'static str,
305 struct PtrArg<'tcx> {
310 method_renames: &'static [(&'static str, &'static str)],
311 ref_prefix: RefPrefix,
312 deref_ty: DerefTy<'tcx>,
313 deref_assoc_items: Option<(DefId, &'tcx AssocItems<'tcx>)>,
316 fn build_msg(&self) -> String {
318 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
319 self.ref_prefix.mutability.prefix_str(),
321 self.ref_prefix.mutability.prefix_str(),
322 self.deref_ty.argless_str(),
326 fn mutability(&self) -> Mutability {
327 self.ref_prefix.mutability
333 mutability: Mutability,
335 impl fmt::Display for RefPrefix {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340 LifetimeName::Param(ParamName::Plain(name)) => {
344 LifetimeName::Underscore => f.write_str("'_ ")?,
345 LifetimeName::Static => f.write_str("'static ")?,
348 f.write_str(self.mutability.prefix_str())
352 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
353 impl fmt::Display for DerefTyDisplay<'_, '_> {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357 DerefTy::Str => f.write_str("str"),
358 DerefTy::Path => f.write_str("Path"),
359 DerefTy::Slice(hir_ty, ty) => {
361 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
362 Some(s) => f.write_str(&s)?,
374 Slice(Option<Span>, Ty<'tcx>),
376 impl<'tcx> DerefTy<'tcx> {
377 fn argless_str(&self) -> &'static str {
380 Self::Path => "Path",
381 Self::Slice(..) => "[_]",
385 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
386 DerefTyDisplay(cx, self)
390 fn check_fn_args<'cx, 'tcx: 'cx>(
391 cx: &'cx LateContext<'tcx>,
393 hir_tys: &'tcx [hir::Ty<'_>],
394 params: &'tcx [Param<'_>],
395 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
399 .filter_map(|(i, (ty, hir_ty))| {
401 if let ty::Ref(_, ty, mutability) = *ty.kind();
402 if let ty::Adt(adt, substs) = *ty.kind();
404 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
405 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
407 // Check that the name as typed matches the actual name of the type.
408 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
409 if let [.., name] = path.segments;
410 if cx.tcx.item_name(adt.did()) == name.ident.name;
412 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
413 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
416 let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did()) {
418 [("clone", ".to_owned()")].as_slice(),
421 .and_then(|args| args.args.first())
422 .and_then(|arg| if let GenericArg::Type(ty) = arg {
429 cx.tcx.lang_items().slice_impl()
431 Some(sym::String) => (
432 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
434 cx.tcx.lang_items().str_impl()
436 Some(sym::PathBuf) => (
437 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
441 Some(sym::Cow) if mutability == Mutability::Not => {
442 let ty_name = name.args
444 args.args.iter().find_map(|a| match a {
445 GenericArg::Type(x) => Some(x),
449 .and_then(|arg| snippet_opt(cx, arg.span))
450 .unwrap_or_else(|| substs.type_at(1).to_string());
455 "using a reference to `Cow` is not recommended",
457 format!("&{}{}", mutability.prefix_str(), ty_name),
458 Applicability::Unspecified,
468 ty_name: name.ident.name,
470 ref_prefix: RefPrefix {
475 deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))),
483 fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
484 if let FnRetTy::Return(ty) = sig.decl.output
485 && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
487 let args: Option<Vec<_>> = sig
491 .filter_map(get_rptr_lm)
492 .filter(|&(lt, _, _)| lt.name == out.name)
493 .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
495 if let Some(args) = args
497 && body.map_or(true, |body| {
498 sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
505 "mutable borrow from immutable input(s)",
507 let ms = MultiSpan::from_spans(args);
508 diag.span_note(ms, "immutable borrow here");
515 #[allow(clippy::too_many_lines)]
516 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
517 struct V<'cx, 'tcx> {
518 cx: &'cx LateContext<'tcx>,
519 /// Map from a local id to which argument it came from (index into `Self::args` and
521 bindings: HirIdMap<usize>,
522 /// The arguments being checked.
523 args: &'cx [PtrArg<'tcx>],
524 /// The results for each argument (len should match args.len)
525 results: Vec<PtrArgResult>,
526 /// The number of arguments which can't be linted. Used to return early.
529 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
530 type NestedFilter = nested_filter::OnlyBodies;
531 fn nested_visit_map(&mut self) -> Self::Map {
535 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
537 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
538 if self.skip_count == self.args.len() {
542 // Check if this is local we care about
543 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
545 None => return walk_expr(self, e),
547 let args = &self.args[args_idx];
548 let result = &mut self.results[args_idx];
550 // Helper function to handle early returns.
551 let mut set_skip_flag = || {
553 self.skip_count += 1;
558 match get_expr_use_or_unification_node(self.cx.tcx, e) {
559 Some((Node::Stmt(_), _)) => (),
560 Some((Node::Local(l), _)) => {
561 // Only trace simple bindings. e.g `let x = y;`
562 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
563 self.bindings.insert(id, args_idx);
568 Some((Node::Expr(e), child_id)) => match e.kind {
569 ExprKind::Call(f, expr_args) => {
570 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
571 if expr_sig(self.cx, f)
572 .map(|sig| sig.input(i).skip_binder().peel_refs())
573 .map_or(true, |ty| match *ty.kind() {
574 ty::Param(_) => true,
575 ty::Adt(def, _) => def.did() == args.ty_did,
579 // Passed to a function taking the non-dereferenced type.
583 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
584 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
586 // Check if the method can be renamed.
587 let name = name.ident.as_str();
588 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
589 result.replacements.push(PtrArgReplacement {
591 self_span: self_arg.span,
598 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
605 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
609 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
612 if def.did() == args.ty_did
614 || self.cx.tcx.trait_of_item(id).is_some()
615 || !args.deref_assoc_items.map_or(false, |(id, items)| {
617 .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id)
626 // Indexing is fine for currently supported types.
627 ExprKind::Index(e, _) if e.hir_id == child_id => (),
628 _ => set_skip_flag(),
630 _ => set_skip_flag(),
635 let mut skip_count = 0;
636 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
642 .filter_map(|(i, arg)| {
643 let param = &body.params[arg.idx];
644 match param.pat.kind {
645 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
646 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
652 results[i].skip = true;
662 v.visit_expr(&body.value);
666 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
667 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
668 Some((lt, m.mutbl, ty.span))
674 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
675 if let ExprKind::Call(pathexp, []) = expr.kind {
676 path_def_id(cx, pathexp).map_or(false, |id| {
677 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))