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_lint::{LateContext, LateLintPass};
19 use rustc_middle::hir::nested_filter;
20 use rustc_middle::ty::{self, Ty};
21 use rustc_session::{declare_lint_pass, declare_tool_lint};
22 use rustc_span::source_map::Span;
24 use rustc_span::symbol::Symbol;
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.
51 /// fn foo(&Vec<u32>) { .. }
56 /// fn foo(&[u32]) { .. }
58 #[clippy::version = "pre 1.29.0"]
61 "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
64 declare_clippy_lint! {
66 /// This lint checks for equality comparisons with `ptr::null`
68 /// ### Why is this bad?
69 /// It's easier and more readable to use the inherent
77 /// if x == ptr::null {
88 #[clippy::version = "pre 1.29.0"]
91 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
94 declare_clippy_lint! {
96 /// This lint checks for functions that take immutable references and return
97 /// mutable ones. This will not trigger if no unsafe code exists as there
98 /// are multiple safe functions which will do this transformation
100 /// To be on the conservative side, if there's at least one mutable
101 /// reference with the output lifetime, this lint will not trigger.
103 /// ### Why is this bad?
104 /// Creating a mutable reference which can be repeatably derived from an
105 /// immutable reference is unsound as it allows creating multiple live
106 /// mutable references to the same object.
108 /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
109 /// lead to an interim Rust release 1.15.1.
111 /// ### Known problems
112 /// This pattern is used by memory allocators to allow allocating multiple
113 /// objects while returning mutable references to each one. So long as
114 /// different mutable references are returned each time such a function may
119 /// fn foo(&Foo) -> &mut Bar { .. }
121 #[clippy::version = "pre 1.29.0"]
124 "fns that create mutable refs from immutable ref args"
127 declare_clippy_lint! {
129 /// This lint checks for invalid usages of `ptr::null`.
131 /// ### Why is this bad?
132 /// This causes undefined behavior.
136 /// // Undefined behavior
137 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
142 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
144 #[clippy::version = "1.53.0"]
145 pub INVALID_NULL_PTR_USAGE,
147 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
150 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
152 impl<'tcx> LateLintPass<'tcx> for Ptr {
153 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
154 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
155 if matches!(trait_method, TraitFn::Provided(_)) {
156 // Handled by check body.
160 check_mut_from_ref(cx, sig, None);
161 for arg in check_fn_args(
163 cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
167 .filter(|arg| arg.mutability() == Mutability::Not)
169 span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, &arg.build_msg(), |diag| {
170 diag.span_suggestion(
173 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
174 Applicability::Unspecified,
181 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
182 let hir = cx.tcx.hir();
183 let mut parents = hir.parent_iter(body.value.hir_id);
184 let (item_id, sig, is_trait_item) = match parents.next() {
185 Some((_, Node::Item(i))) => {
186 if let ItemKind::Fn(sig, ..) = &i.kind {
187 (i.def_id, sig, false)
192 Some((_, Node::ImplItem(i))) => {
193 if !matches!(parents.next(),
194 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
198 if let ImplItemKind::Fn(sig, _) = &i.kind {
199 (i.def_id, sig, false)
204 Some((_, Node::TraitItem(i))) => {
205 if let TraitItemKind::Fn(sig, _) = &i.kind {
206 (i.def_id, sig, true)
214 check_mut_from_ref(cx, sig, Some(body));
216 let sig = cx.tcx.fn_sig(item_id).skip_binder();
217 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
218 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
220 let results = check_ptr_arg_usage(cx, body, &lint_args);
222 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
223 span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, &args.build_msg(), |diag| {
224 diag.multipart_suggestion(
226 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
227 .chain(result.replacements.iter().map(|r| {
230 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
234 Applicability::Unspecified,
240 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
241 if let ExprKind::Binary(ref op, l, r) = expr.kind {
242 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
247 "comparing with null is better expressed by the `.is_null()` method",
251 check_invalid_ptr_usage(cx, expr);
256 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
257 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
258 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
259 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
260 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
261 (&paths::PTR_COPY, &[0, 1]),
262 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
263 (&paths::PTR_READ, &[0]),
264 (&paths::PTR_READ_UNALIGNED, &[0]),
265 (&paths::PTR_READ_VOLATILE, &[0]),
266 (&paths::PTR_REPLACE, &[0]),
267 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
268 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
269 (&paths::PTR_SWAP, &[0, 1]),
270 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
271 (&paths::PTR_WRITE, &[0]),
272 (&paths::PTR_WRITE_UNALIGNED, &[0]),
273 (&paths::PTR_WRITE_VOLATILE, &[0]),
274 (&paths::PTR_WRITE_BYTES, &[0]),
278 if let ExprKind::Call(fun, args) = expr.kind;
279 if let ExprKind::Path(ref qpath) = fun.kind;
280 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
281 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
282 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
284 .find(|&&(fn_path, _)| fn_path == fun_def_path);
286 for &arg_idx in arg_indices {
287 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
290 INVALID_NULL_PTR_USAGE,
292 "pointer must be non-null",
294 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
295 Applicability::MachineApplicable,
304 struct PtrArgResult {
306 replacements: Vec<PtrArgReplacement>,
309 struct PtrArgReplacement {
312 replacement: &'static str,
315 struct PtrArg<'tcx> {
317 emission_id: hir::HirId,
321 method_renames: &'static [(&'static str, &'static str)],
322 ref_prefix: RefPrefix,
323 deref_ty: DerefTy<'tcx>,
326 fn build_msg(&self) -> String {
328 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
329 self.ref_prefix.mutability.prefix_str(),
331 self.ref_prefix.mutability.prefix_str(),
332 self.deref_ty.argless_str(),
336 fn mutability(&self) -> Mutability {
337 self.ref_prefix.mutability
343 mutability: Mutability,
345 impl fmt::Display for RefPrefix {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 LifetimeName::Param(_, ParamName::Plain(name)) => {
354 LifetimeName::Underscore => f.write_str("'_ ")?,
355 LifetimeName::Static => f.write_str("'static ")?,
358 f.write_str(self.mutability.prefix_str())
362 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
363 impl fmt::Display for DerefTyDisplay<'_, '_> {
364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 DerefTy::Str => f.write_str("str"),
368 DerefTy::Path => f.write_str("Path"),
369 DerefTy::Slice(hir_ty, ty) => {
371 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
372 Some(s) => f.write_str(&s)?,
384 Slice(Option<Span>, Ty<'tcx>),
386 impl<'tcx> DerefTy<'tcx> {
387 fn argless_str(&self) -> &'static str {
390 Self::Path => "Path",
391 Self::Slice(..) => "[_]",
395 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
396 DerefTyDisplay(cx, self)
400 fn check_fn_args<'cx, 'tcx: 'cx>(
401 cx: &'cx LateContext<'tcx>,
402 tys: &'tcx [Ty<'tcx>],
403 hir_tys: &'tcx [hir::Ty<'tcx>],
404 params: &'tcx [Param<'tcx>],
405 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
409 .filter_map(|(i, (ty, hir_ty))| {
411 if let ty::Ref(_, ty, mutability) = *ty.kind();
412 if let ty::Adt(adt, substs) = *ty.kind();
414 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
415 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
417 // Check that the name as typed matches the actual name of the type.
418 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
419 if let [.., name] = path.segments;
420 if cx.tcx.item_name(adt.did()) == name.ident.name;
423 let emission_id = params.get(i).map_or(hir_ty.hir_id, |param| param.hir_id);
424 let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
426 [("clone", ".to_owned()")].as_slice(),
429 .and_then(|args| args.args.first())
430 .and_then(|arg| if let GenericArg::Type(ty) = arg {
438 Some(sym::String) => (
439 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
442 Some(sym::PathBuf) => (
443 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
446 Some(sym::Cow) if mutability == Mutability::Not => {
447 let ty_name = name.args
449 args.args.iter().find_map(|a| match a {
450 GenericArg::Type(x) => Some(x),
454 .and_then(|arg| snippet_opt(cx, arg.span))
455 .unwrap_or_else(|| substs.type_at(1).to_string());
456 span_lint_hir_and_then(
461 "using a reference to `Cow` is not recommended",
463 diag.span_suggestion(
466 format!("&{}{}", mutability.prefix_str(), ty_name),
467 Applicability::Unspecified,
480 ty_name: name.ident.name,
482 ref_prefix: RefPrefix {
494 fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
495 if let FnRetTy::Return(ty) = sig.decl.output
496 && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
498 let args: Option<Vec<_>> = sig
502 .filter_map(get_rptr_lm)
503 .filter(|&(lt, _, _)| lt.name == out.name)
504 .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
506 if let Some(args) = args
508 && body.map_or(true, |body| {
509 sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
516 "mutable borrow from immutable input(s)",
518 let ms = MultiSpan::from_spans(args);
519 diag.span_note(ms, "immutable borrow here");
526 #[expect(clippy::too_many_lines)]
527 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
528 struct V<'cx, 'tcx> {
529 cx: &'cx LateContext<'tcx>,
530 /// Map from a local id to which argument it came from (index into `Self::args` and
532 bindings: HirIdMap<usize>,
533 /// The arguments being checked.
534 args: &'cx [PtrArg<'tcx>],
535 /// The results for each argument (len should match args.len)
536 results: Vec<PtrArgResult>,
537 /// The number of arguments which can't be linted. Used to return early.
540 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
541 type NestedFilter = nested_filter::OnlyBodies;
542 fn nested_visit_map(&mut self) -> Self::Map {
546 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
548 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
549 if self.skip_count == self.args.len() {
553 // Check if this is local we care about
554 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
556 None => return walk_expr(self, e),
558 let args = &self.args[args_idx];
559 let result = &mut self.results[args_idx];
561 // Helper function to handle early returns.
562 let mut set_skip_flag = || {
564 self.skip_count += 1;
569 match get_expr_use_or_unification_node(self.cx.tcx, e) {
570 Some((Node::Stmt(_), _)) => (),
571 Some((Node::Local(l), _)) => {
572 // Only trace simple bindings. e.g `let x = y;`
573 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
574 self.bindings.insert(id, args_idx);
579 Some((Node::Expr(e), child_id)) => match e.kind {
580 ExprKind::Call(f, expr_args) => {
581 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
582 if expr_sig(self.cx, f).and_then(|sig| sig.input(i)).map_or(true, |ty| {
583 match *ty.skip_binder().peel_refs().kind() {
584 ty::Param(_) => true,
585 ty::Adt(def, _) => def.did() == args.ty_did,
589 // Passed to a function taking the non-dereferenced type.
593 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
594 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
596 // Check if the method can be renamed.
597 let name = name.ident.as_str();
598 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
599 result.replacements.push(PtrArgReplacement {
601 self_span: self_arg.span,
608 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
615 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
619 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
621 ty::Adt(def, _) if def.did() == args.ty_did => {
627 // Indexing is fine for currently supported types.
628 ExprKind::Index(e, _) if e.hir_id == child_id => (),
629 _ => set_skip_flag(),
631 _ => set_skip_flag(),
636 let mut skip_count = 0;
637 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
643 .filter_map(|(i, arg)| {
644 let param = &body.params[arg.idx];
645 match param.pat.kind {
646 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
647 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
653 results[i].skip = true;
663 v.visit_expr(&body.value);
667 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
668 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
669 Some((lt, m.mutbl, ty.span))
675 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
676 if let ExprKind::Call(pathexp, []) = expr.kind {
677 path_def_id(cx, pathexp).map_or(false, |id| {
678 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))