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.
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 references and return
93 /// mutable ones. This will not trigger if no unsafe code exists as there
94 /// are multiple safe functions which will do this transformation
96 /// To be on the conservative side, if there's at least one mutable
97 /// reference with the output lifetime, this lint will not trigger.
99 /// ### Why is this bad?
100 /// Creating a mutable reference which can be repeatably derived from an
101 /// immutable reference is unsound as it allows creating multiple live
102 /// mutable references to the same object.
104 /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
105 /// lead to an interim Rust release 1.15.1.
107 /// ### Known problems
108 /// This pattern is used by memory allocators to allow allocating multiple
109 /// objects while returning mutable references to each one. So long as
110 /// different mutable references are returned each time such a function may
115 /// fn foo(&Foo) -> &mut Bar { .. }
117 #[clippy::version = "pre 1.29.0"]
120 "fns that create mutable refs from immutable ref args"
123 declare_clippy_lint! {
125 /// This lint checks for invalid usages of `ptr::null`.
127 /// ### Why is this bad?
128 /// This causes undefined behavior.
132 /// // Bad. Undefined behavior
133 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
138 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
140 #[clippy::version = "1.53.0"]
141 pub INVALID_NULL_PTR_USAGE,
143 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
146 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
148 impl<'tcx> LateLintPass<'tcx> for Ptr {
149 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
150 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
151 if matches!(trait_method, TraitFn::Provided(_)) {
152 // Handled by check body.
156 check_mut_from_ref(cx, sig, None);
157 for arg in check_fn_args(
159 cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
163 .filter(|arg| arg.mutability() == Mutability::Not)
171 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
172 Applicability::Unspecified,
178 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
179 let hir = cx.tcx.hir();
180 let mut parents = hir.parent_iter(body.value.hir_id);
181 let (item_id, sig, is_trait_item) = match parents.next() {
182 Some((_, Node::Item(i))) => {
183 if let ItemKind::Fn(sig, ..) = &i.kind {
184 (i.def_id, sig, false)
189 Some((_, Node::ImplItem(i))) => {
190 if !matches!(parents.next(),
191 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
195 if let ImplItemKind::Fn(sig, _) = &i.kind {
196 (i.def_id, sig, false)
201 Some((_, Node::TraitItem(i))) => {
202 if let TraitItemKind::Fn(sig, _) = &i.kind {
203 (i.def_id, sig, true)
211 check_mut_from_ref(cx, sig, Some(body));
213 let sig = cx.tcx.fn_sig(item_id).skip_binder();
214 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params)
215 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
217 let results = check_ptr_arg_usage(cx, body, &lint_args);
219 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
220 span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
221 diag.multipart_suggestion(
223 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
224 .chain(result.replacements.iter().map(|r| {
227 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
231 Applicability::Unspecified,
237 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
238 if let ExprKind::Binary(ref op, l, r) = expr.kind {
239 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
244 "comparing with null is better expressed by the `.is_null()` method",
248 check_invalid_ptr_usage(cx, expr);
253 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
254 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
255 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
256 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
257 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
258 (&paths::PTR_COPY, &[0, 1]),
259 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
260 (&paths::PTR_READ, &[0]),
261 (&paths::PTR_READ_UNALIGNED, &[0]),
262 (&paths::PTR_READ_VOLATILE, &[0]),
263 (&paths::PTR_REPLACE, &[0]),
264 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
265 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
266 (&paths::PTR_SWAP, &[0, 1]),
267 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
268 (&paths::PTR_WRITE, &[0]),
269 (&paths::PTR_WRITE_UNALIGNED, &[0]),
270 (&paths::PTR_WRITE_VOLATILE, &[0]),
271 (&paths::PTR_WRITE_BYTES, &[0]),
275 if let ExprKind::Call(fun, args) = expr.kind;
276 if let ExprKind::Path(ref qpath) = fun.kind;
277 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
278 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
279 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
281 .find(|&&(fn_path, _)| fn_path == fun_def_path);
283 for &arg_idx in arg_indices {
284 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
287 INVALID_NULL_PTR_USAGE,
289 "pointer must be non-null",
291 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
292 Applicability::MachineApplicable,
301 struct PtrArgResult {
303 replacements: Vec<PtrArgReplacement>,
306 struct PtrArgReplacement {
309 replacement: &'static str,
312 struct PtrArg<'tcx> {
317 method_renames: &'static [(&'static str, &'static str)],
318 ref_prefix: RefPrefix,
319 deref_ty: DerefTy<'tcx>,
322 fn build_msg(&self) -> String {
324 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
325 self.ref_prefix.mutability.prefix_str(),
327 self.ref_prefix.mutability.prefix_str(),
328 self.deref_ty.argless_str(),
332 fn mutability(&self) -> Mutability {
333 self.ref_prefix.mutability
339 mutability: Mutability,
341 impl fmt::Display for RefPrefix {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 LifetimeName::Param(ParamName::Plain(name)) => {
350 LifetimeName::Underscore => f.write_str("'_ ")?,
351 LifetimeName::Static => f.write_str("'static ")?,
354 f.write_str(self.mutability.prefix_str())
358 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
359 impl fmt::Display for DerefTyDisplay<'_, '_> {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 DerefTy::Str => f.write_str("str"),
364 DerefTy::Path => f.write_str("Path"),
365 DerefTy::Slice(hir_ty, ty) => {
367 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
368 Some(s) => f.write_str(&s)?,
380 Slice(Option<Span>, Ty<'tcx>),
382 impl<'tcx> DerefTy<'tcx> {
383 fn argless_str(&self) -> &'static str {
386 Self::Path => "Path",
387 Self::Slice(..) => "[_]",
391 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
392 DerefTyDisplay(cx, self)
396 fn check_fn_args<'cx, 'tcx: 'cx>(
397 cx: &'cx LateContext<'tcx>,
399 hir_tys: &'tcx [hir::Ty<'_>],
400 params: &'tcx [Param<'_>],
401 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
405 .filter_map(|(i, (ty, hir_ty))| {
407 if let ty::Ref(_, ty, mutability) = *ty.kind();
408 if let ty::Adt(adt, substs) = *ty.kind();
410 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
411 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
413 // Check that the name as typed matches the actual name of the type.
414 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
415 if let [.., name] = path.segments;
416 if cx.tcx.item_name(adt.did()) == name.ident.name;
418 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
419 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
422 let (method_renames, deref_ty) = match cx.tcx.get_diagnostic_name(adt.did()) {
424 [("clone", ".to_owned()")].as_slice(),
427 .and_then(|args| args.args.first())
428 .and_then(|arg| if let GenericArg::Type(ty) = arg {
436 Some(sym::String) => (
437 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
440 Some(sym::PathBuf) => (
441 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
444 Some(sym::Cow) if mutability == Mutability::Not => {
445 let ty_name = name.args
447 args.args.iter().find_map(|a| match a {
448 GenericArg::Type(x) => Some(x),
452 .and_then(|arg| snippet_opt(cx, arg.span))
453 .unwrap_or_else(|| substs.type_at(1).to_string());
458 "using a reference to `Cow` is not recommended",
460 format!("&{}{}", mutability.prefix_str(), ty_name),
461 Applicability::Unspecified,
471 ty_name: name.ident.name,
473 ref_prefix: RefPrefix {
485 fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&'tcx Body<'_>>) {
486 if let FnRetTy::Return(ty) = sig.decl.output
487 && let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty)
489 let args: Option<Vec<_>> = sig
493 .filter_map(get_rptr_lm)
494 .filter(|&(lt, _, _)| lt.name == out.name)
495 .map(|(_, mutability, span)| (mutability == Mutability::Not).then(|| span))
497 if let Some(args) = args
499 && body.map_or(true, |body| {
500 sig.header.unsafety == Unsafety::Unsafe || contains_unsafe_block(cx, &body.value)
507 "mutable borrow from immutable input(s)",
509 let ms = MultiSpan::from_spans(args);
510 diag.span_note(ms, "immutable borrow here");
517 #[expect(clippy::too_many_lines)]
518 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
519 struct V<'cx, 'tcx> {
520 cx: &'cx LateContext<'tcx>,
521 /// Map from a local id to which argument it came from (index into `Self::args` and
523 bindings: HirIdMap<usize>,
524 /// The arguments being checked.
525 args: &'cx [PtrArg<'tcx>],
526 /// The results for each argument (len should match args.len)
527 results: Vec<PtrArgResult>,
528 /// The number of arguments which can't be linted. Used to return early.
531 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
532 type NestedFilter = nested_filter::OnlyBodies;
533 fn nested_visit_map(&mut self) -> Self::Map {
537 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
539 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
540 if self.skip_count == self.args.len() {
544 // Check if this is local we care about
545 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
547 None => return walk_expr(self, e),
549 let args = &self.args[args_idx];
550 let result = &mut self.results[args_idx];
552 // Helper function to handle early returns.
553 let mut set_skip_flag = || {
555 self.skip_count += 1;
560 match get_expr_use_or_unification_node(self.cx.tcx, e) {
561 Some((Node::Stmt(_), _)) => (),
562 Some((Node::Local(l), _)) => {
563 // Only trace simple bindings. e.g `let x = y;`
564 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
565 self.bindings.insert(id, args_idx);
570 Some((Node::Expr(e), child_id)) => match e.kind {
571 ExprKind::Call(f, expr_args) => {
572 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
573 if expr_sig(self.cx, f)
574 .map(|sig| sig.input(i).skip_binder().peel_refs())
575 .map_or(true, |ty| match *ty.kind() {
576 ty::Param(_) => true,
577 ty::Adt(def, _) => def.did() == args.ty_did,
581 // Passed to a function taking the non-dereferenced type.
585 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
586 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
588 // Check if the method can be renamed.
589 let name = name.ident.as_str();
590 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
591 result.replacements.push(PtrArgReplacement {
593 self_span: self_arg.span,
600 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
607 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
611 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
613 ty::Adt(def, _) if def.did() == args.ty_did => {
619 // Indexing is fine for currently supported types.
620 ExprKind::Index(e, _) if e.hir_id == child_id => (),
621 _ => set_skip_flag(),
623 _ => set_skip_flag(),
628 let mut skip_count = 0;
629 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
635 .filter_map(|(i, arg)| {
636 let param = &body.params[arg.idx];
637 match param.pat.kind {
638 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
639 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
645 results[i].skip = true;
655 v.visit_expr(&body.value);
659 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
660 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
661 Some((lt, m.mutbl, ty.span))
667 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
668 if let ExprKind::Call(pathexp, []) = expr.kind {
669 path_def_id(cx, pathexp).map_or(false, |id| {
670 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))