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::{get_expr_use_or_unification_node, is_lint_allowed, path_def_id, path_to_local, paths};
7 use if_chain::if_chain;
8 use rustc_errors::Applicability;
9 use rustc_hir::def_id::DefId;
10 use rustc_hir::hir_id::HirIdMap;
11 use rustc_hir::intravisit::{walk_expr, Visitor};
13 self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg,
14 ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
15 TraitItem, TraitItemKind, TyKind,
17 use rustc_lint::{LateContext, LateLintPass};
18 use rustc_middle::hir::nested_filter;
19 use rustc_middle::ty::{self, AssocItems, AssocKind, Ty};
20 use rustc_session::{declare_lint_pass, declare_tool_lint};
21 use rustc_span::source_map::Span;
22 use rustc_span::symbol::Symbol;
23 use rustc_span::{sym, MultiSpan};
27 declare_clippy_lint! {
29 /// This lint checks for function arguments of type `&String`, `&Vec`,
30 /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
31 /// with the appropriate `.to_owned()`/`to_string()` calls.
33 /// ### Why is this bad?
34 /// Requiring the argument to be of the specific size
35 /// makes the function less useful for no benefit; slices in the form of `&[T]`
36 /// or `&str` usually suffice and can be obtained from other types, too.
38 /// ### Known problems
39 /// There may be `fn(&Vec)`-typed references pointing to your function.
40 /// If you have them, you will get a compiler error after applying this lint's
41 /// suggestions. You then have the choice to undo your changes or change the
42 /// type of the reference.
44 /// Note that if the function is part of your public interface, there may be
45 /// other crates referencing it, of which you may not be aware. Carefully
46 /// deprecate the function before applying the lint suggestions in this case.
51 /// fn foo(&Vec<u32>) { .. }
54 /// fn foo(&[u32]) { .. }
56 #[clippy::version = "pre 1.29.0"]
59 "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
62 declare_clippy_lint! {
64 /// This lint checks for equality comparisons with `ptr::null`
66 /// ### Why is this bad?
67 /// It's easier and more readable to use the inherent
74 /// if x == ptr::null {
83 #[clippy::version = "pre 1.29.0"]
86 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
89 declare_clippy_lint! {
91 /// This lint checks for functions that take immutable
92 /// references and return mutable ones.
94 /// ### Why is this bad?
95 /// This is trivially unsound, as one can create two
96 /// mutable references from the same (immutable!) source.
97 /// This [error](https://github.com/rust-lang/rust/issues/39465)
98 /// actually lead to an interim Rust release 1.15.1.
100 /// ### Known problems
101 /// To be on the conservative side, if there's at least one
102 /// mutable reference with the output lifetime, this lint will not trigger.
103 /// In practice, this case is unlikely anyway.
107 /// fn foo(&Foo) -> &mut Bar { .. }
109 #[clippy::version = "pre 1.29.0"]
112 "fns that create mutable refs from immutable ref args"
115 declare_clippy_lint! {
117 /// This lint checks for invalid usages of `ptr::null`.
119 /// ### Why is this bad?
120 /// This causes undefined behavior.
124 /// // Bad. Undefined behavior
125 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
130 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
132 #[clippy::version = "1.53.0"]
133 pub INVALID_NULL_PTR_USAGE,
135 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
138 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
140 impl<'tcx> LateLintPass<'tcx> for Ptr {
141 fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
142 if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
143 if matches!(trait_method, TraitFn::Provided(_)) {
144 // Handled by check body.
148 check_mut_from_ref(cx, sig.decl);
149 for arg in check_fn_args(
151 cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
155 .filter(|arg| arg.mutability() == Mutability::Not)
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, is_trait_item) = match parents.next() {
174 Some((_, Node::Item(i))) => {
175 if let ItemKind::Fn(sig, ..) = &i.kind {
176 (i.def_id, sig.decl, false)
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 {
188 (i.def_id, sig.decl, false)
193 Some((_, Node::TraitItem(i))) => {
194 if let TraitItemKind::Fn(sig, _) = &i.kind {
195 (i.def_id, sig.decl, true)
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)
206 .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not)
208 let results = check_ptr_arg_usage(cx, body, &lint_args);
210 for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
211 span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
212 diag.multipart_suggestion(
214 iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
215 .chain(result.replacements.iter().map(|r| {
218 format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
222 Applicability::Unspecified,
228 fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
229 if let ExprKind::Binary(ref op, l, r) = expr.kind {
230 if (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) && (is_null_path(cx, l) || is_null_path(cx, r)) {
235 "comparing with null is better expressed by the `.is_null()` method",
239 check_invalid_ptr_usage(cx, expr);
244 fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
245 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
246 const INVALID_NULL_PTR_USAGE_TABLE: [(&[&str], &[usize]); 16] = [
247 (&paths::SLICE_FROM_RAW_PARTS, &[0]),
248 (&paths::SLICE_FROM_RAW_PARTS_MUT, &[0]),
249 (&paths::PTR_COPY, &[0, 1]),
250 (&paths::PTR_COPY_NONOVERLAPPING, &[0, 1]),
251 (&paths::PTR_READ, &[0]),
252 (&paths::PTR_READ_UNALIGNED, &[0]),
253 (&paths::PTR_READ_VOLATILE, &[0]),
254 (&paths::PTR_REPLACE, &[0]),
255 (&paths::PTR_SLICE_FROM_RAW_PARTS, &[0]),
256 (&paths::PTR_SLICE_FROM_RAW_PARTS_MUT, &[0]),
257 (&paths::PTR_SWAP, &[0, 1]),
258 (&paths::PTR_SWAP_NONOVERLAPPING, &[0, 1]),
259 (&paths::PTR_WRITE, &[0]),
260 (&paths::PTR_WRITE_UNALIGNED, &[0]),
261 (&paths::PTR_WRITE_VOLATILE, &[0]),
262 (&paths::PTR_WRITE_BYTES, &[0]),
266 if let ExprKind::Call(fun, args) = expr.kind;
267 if let ExprKind::Path(ref qpath) = fun.kind;
268 if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
269 let fun_def_path = cx.get_def_path(fun_def_id).into_iter().map(Symbol::to_ident_string).collect::<Vec<_>>();
270 if let Some(&(_, arg_indices)) = INVALID_NULL_PTR_USAGE_TABLE
272 .find(|&&(fn_path, _)| fn_path == fun_def_path);
274 for &arg_idx in arg_indices {
275 if let Some(arg) = args.get(arg_idx).filter(|arg| is_null_path(cx, arg)) {
278 INVALID_NULL_PTR_USAGE,
280 "pointer must be non-null",
282 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
283 Applicability::MachineApplicable,
292 struct PtrArgResult {
294 replacements: Vec<PtrArgReplacement>,
297 struct PtrArgReplacement {
300 replacement: &'static str,
303 struct PtrArg<'tcx> {
308 method_renames: &'static [(&'static str, &'static str)],
309 ref_prefix: RefPrefix,
310 deref_ty: DerefTy<'tcx>,
311 deref_assoc_items: Option<(DefId, &'tcx AssocItems<'tcx>)>,
314 fn build_msg(&self) -> String {
316 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
317 self.ref_prefix.mutability.prefix_str(),
319 self.ref_prefix.mutability.prefix_str(),
320 self.deref_ty.argless_str(),
324 fn mutability(&self) -> Mutability {
325 self.ref_prefix.mutability
331 mutability: Mutability,
333 impl fmt::Display for RefPrefix {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338 LifetimeName::Param(ParamName::Plain(name)) => {
342 LifetimeName::Underscore => f.write_str("'_ ")?,
343 LifetimeName::Static => f.write_str("'static ")?,
346 f.write_str(self.mutability.prefix_str())
350 struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
351 impl fmt::Display for DerefTyDisplay<'_, '_> {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 DerefTy::Str => f.write_str("str"),
356 DerefTy::Path => f.write_str("Path"),
357 DerefTy::Slice(hir_ty, ty) => {
359 match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
360 Some(s) => f.write_str(&s)?,
372 Slice(Option<Span>, Ty<'tcx>),
374 impl<'tcx> DerefTy<'tcx> {
375 fn argless_str(&self) -> &'static str {
378 Self::Path => "Path",
379 Self::Slice(..) => "[_]",
383 fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
384 DerefTyDisplay(cx, self)
388 fn check_fn_args<'cx, 'tcx: 'cx>(
389 cx: &'cx LateContext<'tcx>,
391 hir_tys: &'tcx [hir::Ty<'_>],
392 params: &'tcx [Param<'_>],
393 ) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
397 .filter_map(|(i, (ty, hir_ty))| {
399 if let ty::Ref(_, ty, mutability) = *ty.kind();
400 if let ty::Adt(adt, substs) = *ty.kind();
402 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
403 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
405 // Check that the name as typed matches the actual name of the type.
406 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
407 if let [.., name] = path.segments;
408 if cx.tcx.item_name(adt.did) == name.ident.name;
410 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
411 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
414 let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
416 [("clone", ".to_owned()")].as_slice(),
419 .and_then(|args| args.args.first())
420 .and_then(|arg| if let GenericArg::Type(ty) = arg {
427 cx.tcx.lang_items().slice_impl()
429 Some(sym::String) => (
430 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
432 cx.tcx.lang_items().str_impl()
434 Some(sym::PathBuf) => (
435 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
440 let ty_name = name.args
442 args.args.iter().find_map(|a| match a {
443 GenericArg::Type(x) => Some(x),
447 .and_then(|arg| snippet_opt(cx, arg.span))
448 .unwrap_or_else(|| substs.type_at(1).to_string());
453 "using a reference to `Cow` is not recommended",
455 format!("&{}{}", mutability.prefix_str(), ty_name),
456 Applicability::Unspecified,
466 ty_name: name.ident.name,
468 ref_prefix: RefPrefix {
473 deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))),
481 fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
482 if let FnRetTy::Return(ty) = decl.output {
483 if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
484 let mut immutables = vec![];
485 for (_, mutbl, argspan) in decl
488 .filter_map(get_rptr_lm)
489 .filter(|&(lt, _, _)| lt.name == out.name)
491 if mutbl == Mutability::Mut {
494 immutables.push(argspan);
496 if immutables.is_empty() {
503 "mutable borrow from immutable input(s)",
505 let ms = MultiSpan::from_spans(immutables);
506 diag.span_note(ms, "immutable borrow here");
513 #[allow(clippy::too_many_lines)]
514 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
515 struct V<'cx, 'tcx> {
516 cx: &'cx LateContext<'tcx>,
517 /// Map from a local id to which argument it came from (index into `Self::args` and
519 bindings: HirIdMap<usize>,
520 /// The arguments being checked.
521 args: &'cx [PtrArg<'tcx>],
522 /// The results for each argument (len should match args.len)
523 results: Vec<PtrArgResult>,
524 /// The number of arguments which can't be linted. Used to return early.
527 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
528 type NestedFilter = nested_filter::OnlyBodies;
529 fn nested_visit_map(&mut self) -> Self::Map {
533 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
535 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
536 if self.skip_count == self.args.len() {
540 // Check if this is local we care about
541 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
543 None => return walk_expr(self, e),
545 let args = &self.args[args_idx];
546 let result = &mut self.results[args_idx];
548 // Helper function to handle early returns.
549 let mut set_skip_flag = || {
551 self.skip_count += 1;
556 match get_expr_use_or_unification_node(self.cx.tcx, e) {
557 Some((Node::Stmt(_), _)) => (),
558 Some((Node::Local(l), _)) => {
559 // Only trace simple bindings. e.g `let x = y;`
560 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
561 self.bindings.insert(id, args_idx);
566 Some((Node::Expr(e), child_id)) => match e.kind {
567 ExprKind::Call(f, expr_args) => {
568 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
569 if expr_sig(self.cx, f)
570 .map(|sig| sig.input(i).skip_binder().peel_refs())
571 .map_or(true, |ty| match *ty.kind() {
572 ty::Param(_) => true,
573 ty::Adt(def, _) => def.did == args.ty_did,
577 // Passed to a function taking the non-dereferenced type.
581 ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
582 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
584 // Check if the method can be renamed.
585 let name = name.ident.as_str();
586 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
587 result.replacements.push(PtrArgReplacement {
589 self_span: self_arg.span,
596 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
603 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
607 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
610 if def.did == args.ty_did
612 || self.cx.tcx.trait_of_item(id).is_some()
613 || !args.deref_assoc_items.map_or(false, |(id, items)| {
615 .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id)
624 // Indexing is fine for currently supported types.
625 ExprKind::Index(e, _) if e.hir_id == child_id => (),
626 _ => set_skip_flag(),
628 _ => set_skip_flag(),
633 let mut skip_count = 0;
634 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
640 .filter_map(|(i, arg)| {
641 let param = &body.params[arg.idx];
642 match param.pat.kind {
643 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
644 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
650 results[i].skip = true;
660 v.visit_expr(&body.value);
664 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
665 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
666 Some((lt, m.mutbl, ty.span))
672 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
673 if let ExprKind::Call(pathexp, []) = expr.kind {
674 path_def_id(cx, pathexp).map_or(false, |id| {
675 matches!(cx.tcx.get_diagnostic_name(id), Some(sym::ptr_null | sym::ptr_null_mut))