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, 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)
175 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
176 Applicability::Unspecified,
182 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
183 let hir = cx.tcx.hir();
184 let mut parents = hir.parent_iter(body.value.hir_id);
185 let (item_id, sig, is_trait_item) = match parents.next() {
186 Some((_, Node::Item(i))) => {
187 if let ItemKind::Fn(sig, ..) = &i.kind {
188 (i.def_id, sig, false)
193 Some((_, Node::ImplItem(i))) => {
194 if !matches!(parents.next(),
195 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
199 if let ImplItemKind::Fn(sig, _) = &i.kind {
200 (i.def_id, sig, false)
205 Some((_, Node::TraitItem(i))) => {
206 if let TraitItemKind::Fn(sig, _) = &i.kind {
207 (i.def_id, sig, true)
215 check_mut_from_ref(cx, sig, Some(body));
217 let sig = cx.tcx.fn_sig(item_id).skip_binder();
218 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
219 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
221 let results = check_ptr_arg_usage(cx, body, &lint_args);
223 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
224 span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
225 diag.multipart_suggestion(
227 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
228 .chain(result.replacements.iter().map(|r| {
231 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
235 Applicability::Unspecified,
241 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
242 if let ExprKind::Binary(ref op, l, r) = expr.kind {
243 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
248 "comparing with null is better expressed by the `.is_null()` method",
252 check_invalid_ptr_usage(cx, expr);
257 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
258 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
259 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
260 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
261 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
262 (&paths::PTR_COPY, &[0, 1]),
263 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
264 (&paths::PTR_READ, &[0]),
265 (&paths::PTR_READ_UNALIGNED, &[0]),
266 (&paths::PTR_READ_VOLATILE, &[0]),
267 (&paths::PTR_REPLACE, &[0]),
268 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
269 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
270 (&paths::PTR_SWAP, &[0, 1]),
271 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
272 (&paths::PTR_WRITE, &[0]),
273 (&paths::PTR_WRITE_UNALIGNED, &[0]),
274 (&paths::PTR_WRITE_VOLATILE, &[0]),
275 (&paths::PTR_WRITE_BYTES, &[0]),
279 if let ExprKind::Call(fun, args) = expr.kind;
280 if let ExprKind::Path(ref qpath) = fun.kind;
281 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
282 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
283 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
285 .find(|&&(fn_path, _)| fn_path == fun_def_path);
287 for &arg_idx in arg_indices {
288 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
291 INVALID_NULL_PTR_USAGE,
293 "pointer must be non-null",
295 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
296 Applicability::MachineApplicable,
305 struct PtrArgResult {
307 replacements: Vec<PtrArgReplacement>,
310 struct PtrArgReplacement {
313 replacement: &'static str,
316 struct PtrArg<'tcx> {
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;
422 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
423 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
426 let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
428 [("clone", ".to_owned()")].as_slice(),
431 .and_then(|args| args.args.first())
432 .and_then(|arg| if let GenericArg::Type(ty) = arg {
440 Some(sym::String) => (
441 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
444 Some(sym::PathBuf) => (
445 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
448 Some(sym::Cow) if mutability == Mutability::Not => {
449 let ty_name = name.args
451 args.args.iter().find_map(|a| match a {
452 GenericArg::Type(x) => Some(x),
456 .and_then(|arg| snippet_opt(cx, arg.span))
457 .unwrap_or_else(|| substs.type_at(1).to_string());
462 "using a reference to `Cow` is not recommended",
464 format!("&{}{}", mutability.prefix_str(), ty_name),
465 Applicability::Unspecified,
475 ty_name: name.ident.name,
477 ref_prefix: RefPrefix {
489 fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
490 if let FnRetTy::Return(ty) = sig.decl.output
491 && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
493 let args: Option<Vec<_>> = sig
497 .filter_map(get_rptr_lm)
498 .filter(|&(lt, _, _)| lt.name == out.name)
499 .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
501 if let Some(args) = args
503 && body.map_or(true, |body| {
504 sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
511 "mutable borrow from immutable input(s)",
513 let ms = MultiSpan::from_spans(args);
514 diag.span_note(ms, "immutable borrow here");
521 #[expect(clippy::too_many_lines)]
522 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
523 struct V<'cx, 'tcx> {
524 cx: &'cx LateContext<'tcx>,
525 /// Map from a local id to which argument it came from (index into `Self::args` and
527 bindings: HirIdMap<usize>,
528 /// The arguments being checked.
529 args: &'cx [PtrArg<'tcx>],
530 /// The results for each argument (len should match args.len)
531 results: Vec<PtrArgResult>,
532 /// The number of arguments which can't be linted. Used to return early.
535 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
536 type NestedFilter = nested_filter::OnlyBodies;
537 fn nested_visit_map(&mut self) -> Self::Map {
541 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
543 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
544 if self.skip_count == self.args.len() {
548 // Check if this is local we care about
549 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
551 None => return walk_expr(self, e),
553 let args = &self.args[args_idx];
554 let result = &mut self.results[args_idx];
556 // Helper function to handle early returns.
557 let mut set_skip_flag = || {
559 self.skip_count += 1;
564 match get_expr_use_or_unification_node(self.cx.tcx, e) {
565 Some((Node::Stmt(_), _)) => (),
566 Some((Node::Local(l), _)) => {
567 // Only trace simple bindings. e.g `let x = y;`
568 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
569 self.bindings.insert(id, args_idx);
574 Some((Node::Expr(e), child_id)) => match e.kind {
575 ExprKind::Call(f, expr_args) => {
576 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
577 if expr_sig(self.cx, f)
578 .map(|sig| sig.input(i).skip_binder().peel_refs())
579 .map_or(true, |ty| match *ty.kind() {
580 ty::Param(_) => true,
581 ty::Adt(def, _) => def.did() == args.ty_did,
585 // Passed to a function taking the non-dereferenced type.
589 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
590 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
592 // Check if the method can be renamed.
593 let name = name.ident.as_str();
594 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
595 result.replacements.push(PtrArgReplacement {
597 self_span: self_arg.span,
604 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
611 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
615 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
617 ty::Adt(def, _) if def.did() == args.ty_did => {
623 // Indexing is fine for currently supported types.
624 ExprKind::Index(e, _) if e.hir_id == child_id => (),
625 _ => set_skip_flag(),
627 _ => set_skip_flag(),
632 let mut skip_count = 0;
633 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
639 .filter_map(|(i, arg)| {
640 let param = &body.params[arg.idx];
641 match param.pat.kind {
642 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
643 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
649 results[i].skip = true;
659 v.visit_expr(&body.value);
663 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
664 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
665 Some((lt, m.mutbl, ty.span))
671 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
672 if let ExprKind::Call(pathexp, []) = expr.kind {
673 path_def_id(cx, pathexp).map_or(false, |id| {
674 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))