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;
7 expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths,
9 use if_chain::if_chain;
10 use rustc_errors::Applicability;
11 use rustc_hir::def_id::DefId;
12 use rustc_hir::hir_id::HirIdMap;
13 use rustc_hir::intravisit::{walk_expr, Visitor};
15 self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg,
16 ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
17 TraitItem, TraitItemKind, TyKind,
19 use rustc_lint::{LateContext, LateLintPass};
20 use rustc_middle::hir::nested_filter;
21 use rustc_middle::ty::{self, AssocItems, AssocKind, Ty};
22 use rustc_session::{declare_lint_pass, declare_tool_lint};
23 use rustc_span::source_map::Span;
24 use rustc_span::symbol::Symbol;
25 use rustc_span::{sym, MultiSpan};
29 declare_clippy_lint! {
31 /// This lint checks for function arguments of type `&String`, `&Vec`,
32 /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
33 /// with the appropriate `.to_owned()`/`to_string()` calls.
35 /// ### Why is this bad?
36 /// Requiring the argument to be of the specific size
37 /// makes the function less useful for no benefit; slices in the form of `&[T]`
38 /// or `&str` usually suffice and can be obtained from other types, too.
40 /// ### Known problems
41 /// There may be `fn(&Vec)`-typed references pointing to your function.
42 /// If you have them, you will get a compiler error after applying this lint's
43 /// suggestions. You then have the choice to undo your changes or change the
44 /// type of the reference.
46 /// Note that if the function is part of your public interface, there may be
47 /// other crates referencing it, of which you may not be aware. Carefully
48 /// deprecate the function before applying the lint suggestions in this case.
53 /// 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
76 /// if x == ptr::null {
85 #[clippy::version = "pre 1.29.0"]
88 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
91 declare_clippy_lint! {
93 /// This lint checks for functions that take immutable
94 /// references and return mutable ones.
96 /// ### Why is this bad?
97 /// This is trivially unsound, as one can create two
98 /// mutable references from the same (immutable!) source.
99 /// This [error](https://github.com/rust-lang/rust/issues/39465)
100 /// actually lead to an interim Rust release 1.15.1.
102 /// ### Known problems
103 /// To be on the conservative side, if there's at least one
104 /// mutable reference with the output lifetime, this lint will not trigger.
105 /// In practice, this case is unlikely anyway.
109 /// fn foo(&Foo) -> &mut Bar { .. }
111 #[clippy::version = "pre 1.29.0"]
114 "fns that create mutable refs from immutable ref args"
117 declare_clippy_lint! {
119 /// This lint checks for invalid usages of `ptr::null`.
121 /// ### Why is this bad?
122 /// This causes undefined behavior.
126 /// // Bad. Undefined behavior
127 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
132 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
134 #[clippy::version = "1.53.0"]
135 pub INVALID_NULL_PTR_USAGE,
137 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
140 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
142 impl<'tcx> LateLintPass<'tcx> for Ptr {
143 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
144 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
145 if matches!(trait_method, TraitFn::Provided(_)) {
146 // Handled by check body.
150 check_mut_from_ref(cx, sig.decl);
151 for arg in check_fn_args(
153 cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
163 format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
164 Applicability::Unspecified,
170 fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
171 let hir = cx.tcx.hir();
172 let mut parents = hir.parent_iter(body.value.hir_id);
173 let (item_id, decl) = match parents.next() {
174 Some((_, Node::Item(i))) => {
175 if let ItemKind::Fn(sig, ..) = &i.kind {
181 Some((_, Node::ImplItem(i))) => {
182 if !matches!(parents.next(),
183 Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
187 if let ImplItemKind::Fn(sig, _) = &i.kind {
193 Some((_, Node::TraitItem(i))) => {
194 if let TraitItemKind::Fn(sig, _) = &i.kind {
203 check_mut_from_ref(cx, decl);
204 let sig = cx.tcx.fn_sig(item_id).skip_binder();
205 let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params).collect();
206 let results = check_ptr_arg_usage(cx, body, &lint_args);
208 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
209 span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
210 diag.multipart_suggestion(
212 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
213 .chain(result.replacements.iter().map(|r| {
216 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
220 Applicability::Unspecified,
226 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
227 if let ExprKind::Binary(ref op, l, r) = expr.kind {
228 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
233 "comparing with null is better expressed by the `.is_null()` method",
237 check_invalid_ptr_usage(cx, expr);
242 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
243 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
244 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
245 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
246 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
247 (&paths::PTR_COPY, &[0, 1]),
248 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
249 (&paths::PTR_READ, &[0]),
250 (&paths::PTR_READ_UNALIGNED, &[0]),
251 (&paths::PTR_READ_VOLATILE, &[0]),
252 (&paths::PTR_REPLACE, &[0]),
253 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
254 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
255 (&paths::PTR_SWAP, &[0, 1]),
256 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
257 (&paths::PTR_WRITE, &[0]),
258 (&paths::PTR_WRITE_UNALIGNED, &[0]),
259 (&paths::PTR_WRITE_VOLATILE, &[0]),
260 (&paths::PTR_WRITE_BYTES, &[0]),
264 if let ExprKind::Call(fun, args) = expr.kind;
265 if let ExprKind::Path(ref qpath) = fun.kind;
266 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
267 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
268 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
270 .find(|&&(fn_path, _)| fn_path == fun_def_path);
272 for &arg_idx in arg_indices {
273 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
276 INVALID_NULL_PTR_USAGE,
278 "pointer must be non-null",
280 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
281 Applicability::MachineApplicable,
290 struct PtrArgResult {
292 replacements: Vec<PtrArgReplacement>,
295 struct PtrArgReplacement {
298 replacement: &'static str,
301 struct PtrArg<'tcx> {
306 method_renames: &'static [(&'static str, &'static str)],
307 ref_prefix: RefPrefix,
308 deref_ty: DerefTy<'tcx>,
309 deref_assoc_items: Option<(DefId, &'tcx AssocItems<'tcx>)>,
312 fn build_msg(&self) -> String {
314 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
315 self.ref_prefix.mutability.prefix_str(),
317 self.ref_prefix.mutability.prefix_str(),
318 self.deref_ty.argless_str(),
325 mutability: Mutability,
327 impl fmt::Display for RefPrefix {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 LifetimeName::Param(ParamName::Plain(name)) => {
336 LifetimeName::Underscore => f.write_str("'_ ")?,
337 LifetimeName::Static => f.write_str("'static ")?,
340 f.write_str(self.mutability.prefix_str())
344 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
345 impl fmt::Display for DerefTyDisplay<'_, '_> {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 DerefTy::Str => f.write_str("str"),
350 DerefTy::Path => f.write_str("Path"),
351 DerefTy::Slice(hir_ty, ty) => {
353 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
354 Some(s) => f.write_str(&s)?,
366 Slice(Option<Span>, Ty<'tcx>),
368 impl<'tcx> DerefTy<'tcx> {
369 fn argless_str(&self) -> &'static str {
372 Self::Path => "Path",
373 Self::Slice(..) => "[_]",
377 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
378 DerefTyDisplay(cx, self)
382 fn check_fn_args<'cx, 'tcx: 'cx>(
383 cx: &'cx LateContext<'tcx>,
385 hir_tys: &'tcx [hir::Ty<'_>],
386 params: &'tcx [Param<'_>],
387 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
391 .filter_map(|(i, (ty, hir_ty))| {
393 if let ty::Ref(_, ty, mutability) = *ty.kind();
394 if let ty::Adt(adt, substs) = *ty.kind();
396 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
397 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
399 // Check that the name as typed matches the actual name of the type.
400 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
401 if let [.., name] = path.segments;
402 if cx.tcx.item_name(adt.did) == name.ident.name;
404 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
405 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
408 let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
410 [("clone", ".to_owned()")].as_slice(),
413 .and_then(|args| args.args.first())
414 .and_then(|arg| if let GenericArg::Type(ty) = arg {
421 cx.tcx.lang_items().slice_impl()
423 Some(sym::String) => (
424 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
426 cx.tcx.lang_items().str_impl()
428 Some(sym::PathBuf) => (
429 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
434 let ty_name = name.args
436 args.args.iter().find_map(|a| match a {
437 GenericArg::Type(x) => Some(x),
441 .and_then(|arg| snippet_opt(cx, arg.span))
442 .unwrap_or_else(|| substs.type_at(1).to_string());
447 "using a reference to `Cow` is not recommended",
449 format!("&{}{}", mutability.prefix_str(), ty_name),
450 Applicability::Unspecified,
460 ty_name: name.ident.name,
462 ref_prefix: RefPrefix {
467 deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))),
475 fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
476 if let FnRetTy::Return(ty) = decl.output {
477 if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
478 let mut immutables = vec![];
479 for (_, mutbl, argspan) in decl
482 .filter_map(get_rptr_lm)
483 .filter(|&(lt, _, _)| lt.name == out.name)
485 if mutbl == Mutability::Mut {
488 immutables.push(argspan);
490 if immutables.is_empty() {
497 "mutable borrow from immutable input(s)",
499 let ms = MultiSpan::from_spans(immutables);
500 diag.span_note(ms, "immutable borrow here");
507 #[allow(clippy::too_many_lines)]
508 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
509 struct V<'cx, 'tcx> {
510 cx: &'cx LateContext<'tcx>,
511 /// Map from a local id to which argument it came from (index into `Self::args` and
513 bindings: HirIdMap<usize>,
514 /// The arguments being checked.
515 args: &'cx [PtrArg<'tcx>],
516 /// The results for each argument (len should match args.len)
517 results: Vec<PtrArgResult>,
518 /// The number of arguments which can't be linted. Used to return early.
521 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
522 type NestedFilter = nested_filter::OnlyBodies;
523 fn nested_visit_map(&mut self) -> Self::Map {
527 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
529 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
530 if self.skip_count == self.args.len() {
534 // Check if this is local we care about
535 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
537 None => return walk_expr(self, e),
539 let args = &self.args[args_idx];
540 let result = &mut self.results[args_idx];
542 // Helper function to handle early returns.
543 let mut set_skip_flag = || {
545 self.skip_count += 1;
550 match get_expr_use_or_unification_node(self.cx.tcx, e) {
551 Some((Node::Stmt(_), _)) => (),
552 Some((Node::Local(l), _)) => {
553 // Only trace simple bindings. e.g `let x = y;`
554 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
555 self.bindings.insert(id, args_idx);
560 Some((Node::Expr(e), child_id)) => match e.kind {
561 ExprKind::Call(f, expr_args) => {
562 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
563 if expr_sig(self.cx, f)
564 .map(|sig| sig.input(i).skip_binder().peel_refs())
565 .map_or(true, |ty| match *ty.kind() {
566 ty::Param(_) => true,
567 ty::Adt(def, _) => def.did == args.ty_did,
571 // Passed to a function taking the non-dereferenced type.
575 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
576 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
578 // Check if the method can be renamed.
579 let name = name.ident.as_str();
580 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
581 result.replacements.push(PtrArgReplacement {
583 self_span: self_arg.span,
590 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
597 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
601 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
604 if def.did == args.ty_did
606 || self.cx.tcx.trait_of_item(id).is_some()
607 || !args.deref_assoc_items.map_or(false, |(id, items)| {
609 .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id)
618 // Indexing is fine for currently supported types.
619 ExprKind::Index(e, _) if e.hir_id == child_id => (),
620 _ => set_skip_flag(),
622 _ => set_skip_flag(),
627 let mut skip_count = 0;
628 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
634 .filter_map(|(i, arg)| {
635 let param = &body.params[arg.idx];
636 match param.pat.kind {
637 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
638 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
644 results[arg.idx].skip = true;
654 v.visit_expr(&body.value);
658 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
659 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
660 Some((lt, m.mutbl, ty.span))
666 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
667 if let ExprKind::Call(pathexp, []) = expr.kind {
668 expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
669 match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()