1 //! Checks for usage of `&Vec[_]` and `&String`.
3 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then, span_lint_hir_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, MultiSpan};
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_infer::infer::TyCtxtInferExt;
19 use rustc_infer::traits::{Obligation, ObligationCause};
20 use rustc_lint::{LateContext, LateLintPass};
21 use rustc_middle::hir::nested_filter;
22 use rustc_middle::ty::{self, Binder, Clause, ExistentialPredicate, List, PredicateKind, Ty};
23 use rustc_session::{declare_lint_pass, declare_tool_lint};
24 use rustc_span::source_map::Span;
26 use rustc_span::symbol::Symbol;
27 use rustc_trait_selection::infer::InferCtxtExt as _;
28 use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _;
32 declare_clippy_lint! {
34 /// This lint checks for function arguments of type `&String`, `&Vec`,
35 /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
36 /// with the appropriate `.to_owned()`/`to_string()` calls.
38 /// ### Why is this bad?
39 /// Requiring the argument to be of the specific size
40 /// makes the function less useful for no benefit; slices in the form of `&[T]`
41 /// or `&str` usually suffice and can be obtained from other types, too.
43 /// ### Known problems
44 /// There may be `fn(&Vec)`-typed references pointing to your function.
45 /// If you have them, you will get a compiler error after applying this lint's
46 /// suggestions. You then have the choice to undo your changes or change the
47 /// type of the reference.
49 /// Note that if the function is part of your public interface, there may be
50 /// other crates referencing it, of which you may not be aware. Carefully
51 /// deprecate the function before applying the lint suggestions in this case.
55 /// fn foo(&Vec<u32>) { .. }
60 /// fn foo(&[u32]) { .. }
62 #[clippy::version = "pre 1.29.0"]
65 "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
68 declare_clippy_lint! {
70 /// This lint checks for equality comparisons with `ptr::null`
72 /// ### Why is this bad?
73 /// It's easier and more readable to use the inherent
81 /// if x == ptr::null {
92 #[clippy::version = "pre 1.29.0"]
95 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
98 declare_clippy_lint! {
100 /// This lint checks for functions that take immutable references and return
101 /// mutable ones. This will not trigger if no unsafe code exists as there
102 /// are multiple safe functions which will do this transformation
104 /// To be on the conservative side, if there's at least one mutable
105 /// reference with the output lifetime, this lint will not trigger.
107 /// ### Why is this bad?
108 /// Creating a mutable reference which can be repeatably derived from an
109 /// immutable reference is unsound as it allows creating multiple live
110 /// mutable references to the same object.
112 /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
113 /// lead to an interim Rust release 1.15.1.
115 /// ### Known problems
116 /// This pattern is used by memory allocators to allow allocating multiple
117 /// objects while returning mutable references to each one. So long as
118 /// different mutable references are returned each time such a function may
123 /// fn foo(&Foo) -> &mut Bar { .. }
125 #[clippy::version = "pre 1.29.0"]
128 "fns that create mutable refs from immutable ref args"
131 declare_clippy_lint! {
133 /// This lint checks for invalid usages of `ptr::null`.
135 /// ### Why is this bad?
136 /// This causes undefined behavior.
140 /// // Undefined behavior
141 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
146 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
148 #[clippy::version = "1.53.0"]
149 pub INVALID_NULL_PTR_USAGE,
151 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
154 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
156 impl<'tcx> LateLintPass<'tcx> for Ptr {
157 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
158 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
159 if matches!(trait_method, TraitFn::Provided(_)) {
160 // Handled by check body.
164 check_mut_from_ref(cx, sig, None);
165 for arg in check_fn_args(
167 cx.tcx.fn_sig(item.owner_id).skip_binder().inputs(),
171 .filter(|arg| arg.mutability() == Mutability::Not)
173 span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| {
174 diag.span_suggestion(
177 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
178 Applicability::Unspecified,
185 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
186 let hir = cx.tcx.hir();
187 let mut parents = hir.parent_iter(body.value.hir_id);
188 let (item_id, sig, is_trait_item) = match parents.next() {
189 Some((_, Node::Item(i))) => {
190 if let ItemKind::Fn(sig, ..) = &i.kind {
191 (i.owner_id, sig, false)
196 Some((_, Node::ImplItem(i))) => {
197 if !matches!(parents.next(),
198 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
202 if let ImplItemKind::Fn(sig, _) = &i.kind {
203 (i.owner_id, sig, false)
208 Some((_, Node::TraitItem(i))) => {
209 if let TraitItemKind::Fn(sig, _) = &i.kind {
210 (i.owner_id, sig, true)
218 check_mut_from_ref(cx, sig, Some(body));
220 let sig = cx.tcx.fn_sig(item_id).skip_binder();
221 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
222 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
224 let results = check_ptr_arg_usage(cx, body, &lint_args);
226 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
227 span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| {
228 diag.multipart_suggestion(
230 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
231 .chain(result.replacements.iter().map(|r| {
234 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
238 Applicability::Unspecified,
244 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
245 if let ExprKind::Binary(ref op, l, r) = expr.kind {
246 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
251 "comparing with null is better expressed by the `.is_null()` method",
255 check_invalid_ptr_usage(cx, expr);
260 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
261 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
262 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
263 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
264 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
265 (&paths::PTR_COPY, &[0, 1]),
266 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
267 (&paths::PTR_READ, &[0]),
268 (&paths::PTR_READ_UNALIGNED, &[0]),
269 (&paths::PTR_READ_VOLATILE, &[0]),
270 (&paths::PTR_REPLACE, &[0]),
271 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
272 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
273 (&paths::PTR_SWAP, &[0, 1]),
274 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
275 (&paths::PTR_WRITE, &[0]),
276 (&paths::PTR_WRITE_UNALIGNED, &[0]),
277 (&paths::PTR_WRITE_VOLATILE, &[0]),
278 (&paths::PTR_WRITE_BYTES, &[0]),
282 if let ExprKind::Call(fun, args) = expr.kind;
283 if let ExprKind::Path(ref qpath) = fun.kind;
284 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
285 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
286 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
288 .find(|&&(fn_path, _)| fn_path == fun_def_path);
290 for &arg_idx in arg_indices {
291 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
294 INVALID_NULL_PTR_USAGE,
296 "pointer must be non-null",
298 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
299 Applicability::MachineApplicable,
308 struct PtrArgResult {
310 replacements: Vec<PtrArgReplacement>,
313 struct PtrArgReplacement {
316 replacement: &'static str,
319 struct PtrArg<'tcx> {
321 emission_id: hir::HirId,
325 method_renames: &'static [(&'static str, &'static str)],
326 ref_prefix: RefPrefix,
327 deref_ty: DerefTy<'tcx>,
330 fn build_msg(&self) -> String {
332 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
333 self.ref_prefix.mutability.prefix_str(),
335 self.ref_prefix.mutability.prefix_str(),
336 self.deref_ty.argless_str(),
340 fn mutability(&self) -> Mutability {
341 self.ref_prefix.mutability
347 mutability: Mutability,
349 impl fmt::Display for RefPrefix {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
354 LifetimeName::Param(_, ParamName::Plain(name)) => {
358 LifetimeName::Infer => f.write_str("'_ ")?,
359 LifetimeName::Static => f.write_str("'static ")?,
362 f.write_str(self.mutability.prefix_str())
366 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
367 impl fmt::Display for DerefTyDisplay<'_, '_> {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371 DerefTy::Str => f.write_str("str"),
372 DerefTy::Path => f.write_str("Path"),
373 DerefTy::Slice(hir_ty, ty) => {
375 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
376 Some(s) => f.write_str(&s)?,
388 Slice(Option<Span>, Ty<'tcx>),
390 impl<'tcx> DerefTy<'tcx> {
391 fn ty(&self, cx: &LateContext<'tcx>) -> Ty<'tcx> {
393 Self::Str => cx.tcx.types.str_,
394 Self::Path => cx.tcx.mk_adt(
395 cx.tcx.adt_def(cx.tcx.get_diagnostic_item(sym::Path).unwrap()),
398 Self::Slice(_, ty) => cx.tcx.mk_slice(ty),
402 fn argless_str(&self) -> &'static str {
405 Self::Path => "Path",
406 Self::Slice(..) => "[_]",
410 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
411 DerefTyDisplay(cx, self)
415 fn check_fn_args<'cx, 'tcx: 'cx>(
416 cx: &'cx LateContext<'tcx>,
417 tys: &'tcx [Ty<'tcx>],
418 hir_tys: &'tcx [hir::Ty<'tcx>],
419 params: &'tcx [Param<'tcx>],
420 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
424 .filter_map(|(i, (ty, hir_ty))| {
426 if let ty::Ref(_, ty, mutability) = *ty.kind();
427 if let ty::Adt(adt, substs) = *ty.kind();
429 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
430 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
432 // Check that the name as typed matches the actual name of the type.
433 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
434 if let [.., name] = path.segments;
435 if cx.tcx.item_name(adt.did()) == name.ident.name;
438 let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
439 let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
441 [("clone", ".to_owned()")].as_slice(),
444 .and_then(|args| args.args.first())
445 .and_then(|arg| if let GenericArg::Type(ty) = arg {
453 _ if Some(adt.did()) == cx.tcx.lang_items().string() => (
454 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
457 Some(sym::PathBuf) => (
458 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
461 Some(sym::Cow) if mutability == Mutability::Not => {
462 let ty_name = name.args
464 args.args.iter().find_map(|a| match a {
465 GenericArg::Type(x) => Some(x),
469 .and_then(|arg| snippet_opt(cx, arg.span))
470 .unwrap_or_else(|| substs.type_at(1).to_string());
471 span_lint_hir_and_then(
476 "using a reference to `Cow` is not recommended",
478 diag.span_suggestion(
481 format!("&{}{ty_name}", mutability.prefix_str()),
482 Applicability::Unspecified,
495 ty_name: name.ident.name,
497 ref_prefix: RefPrefix {
509 fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
510 if let FnRetTy::Return(ty) = sig.decl.output
511 && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
513 let out_region = cx.tcx.named_region(out.hir_id);
514 let args: Option<Vec<_>> = sig
518 .filter_map(get_rptr_lm)
519 .filter(|&(lt, _, _)| cx.tcx.named_region(lt.hir_id) == out_region)
520 .map(|(_, mutability, span)| (mutability == Mutability::Not).then_some(span))
522 if let Some(args) = args
524 && body.map_or(true, |body| {
525 sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, body.value)
532 "mutable borrow from immutable input(s)",
534 let ms = MultiSpan::from_spans(args);
535 diag.span_note(ms, "immutable borrow here");
542 #[expect(clippy::too_many_lines)]
543 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
544 struct V<'cx, 'tcx> {
545 cx: &'cx LateContext<'tcx>,
546 /// Map from a local id to which argument it came from (index into `Self::args` and
548 bindings: HirIdMap<usize>,
549 /// The arguments being checked.
550 args: &'cx [PtrArg<'tcx>],
551 /// The results for each argument (len should match args.len)
552 results: Vec<PtrArgResult>,
553 /// The number of arguments which can't be linted. Used to return early.
556 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
557 type NestedFilter = nested_filter::OnlyBodies;
558 fn nested_visit_map(&mut self) -> Self::Map {
562 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
564 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
565 if self.skip_count == self.args.len() {
569 // Check if this is local we care about
570 let Some(&args_idx) = path_to_local(e).and_then(|id| self.bindings.get(&id)) else {
571 return walk_expr(self, e);
573 let args = &self.args[args_idx];
574 let result = &mut self.results[args_idx];
576 // Helper function to handle early returns.
577 let mut set_skip_flag = || {
579 self.skip_count += 1;
584 match get_expr_use_or_unification_node(self.cx.tcx, e) {
585 Some((Node::Stmt(_), _)) => (),
586 Some((Node::Local(l), _)) => {
587 // Only trace simple bindings. e.g `let x = y;`
588 if let PatKind::Binding(BindingAnnotation::NONE, id, _, None) = l.pat.kind {
589 self.bindings.insert(id, args_idx);
594 Some((Node::Expr(e), child_id)) => match e.kind {
595 ExprKind::Call(f, expr_args) => {
596 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
597 if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
598 match *ty.skip_binder().peel_refs().kind() {
599 ty::Dynamic(preds, _, _) => !matches_preds(self.cx, args.deref_ty.ty(self.cx), preds),
600 ty::Param(_) => true,
601 ty::Adt(def, _) => def.did() == args.ty_did,
605 // Passed to a function taking the non-dereferenced type.
609 ExprKind::MethodCall(name, self_arg, expr_args, _) => {
610 let i = std::iter::once(self_arg)
611 .chain(expr_args.iter())
612 .position(|arg| arg.hir_id == child_id)
615 // Check if the method can be renamed.
616 let name = name.ident.as_str();
617 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
618 result.replacements.push(PtrArgReplacement {
620 self_span: self_arg.span,
627 let Some(id) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) else {
632 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
633 ty::Dynamic(preds, _, _) if !matches_preds(self.cx, args.deref_ty.ty(self.cx), preds) => {
639 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
641 ty::Adt(def, _) if def.did() == args.ty_did => {
647 // Indexing is fine for currently supported types.
648 ExprKind::Index(e, _) if e.hir_id == child_id => (),
649 _ => set_skip_flag(),
651 _ => set_skip_flag(),
656 let mut skip_count = 0;
657 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
663 .filter_map(|(i, arg)| {
664 let param = &body.params[arg.idx];
665 match param.pat.kind {
666 PatKind::Binding(BindingAnnotation::NONE, id, _, None)
667 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
673 results[i].skip = true;
683 v.visit_expr(body.value);
687 fn matches_preds<'tcx>(
688 cx: &LateContext<'tcx>,
690 preds: &'tcx [ty::PolyExistentialPredicate<'tcx>],
692 let infcx = cx.tcx.infer_ctxt().build();
693 preds.iter().all(|&p| match cx.tcx.erase_late_bound_regions(p) {
694 ExistentialPredicate::Trait(p) => infcx
695 .type_implements_trait(p.def_id, [ty.into()].into_iter().chain(p.substs.iter()), cx.param_env)
696 .must_apply_modulo_regions(),
697 ExistentialPredicate::Projection(p) => infcx.predicate_must_hold_modulo_regions(&Obligation::new(
699 ObligationCause::dummy(),
701 cx.tcx.mk_predicate(Binder::dummy(
702 PredicateKind::Clause(Clause::Projection(p.with_self_ty(cx.tcx, ty))),
705 ExistentialPredicate::AutoTrait(p) => infcx
706 .type_implements_trait(p, [ty], cx.param_env)
707 .must_apply_modulo_regions(),
711 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
712 if let TyKind::Rptr(lt, ref m) = ty.kind {
713 Some((lt, m.mutbl, ty.span))
719 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
720 if let ExprKind::Call(pathexp, []) = expr.kind {
721 path_def_id(cx, pathexp).map_or(false, |id| {
722 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))