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, NestedVisitorMap, 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::map::Map;
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 {
388 tys.iter().enumerate().filter_map(|(i, ty)| {
390 if let ty::Ref(_, ty, mutability) = *ty.kind();
391 if let ty::Adt(adt, substs) = *ty.kind();
393 let hir_ty = &hir_tys[i];
394 if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
395 if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
397 if let [.., name] = path.segments;
398 if cx.tcx.item_name(adt.did) == name.ident.name;
400 if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
401 if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
404 let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
406 [("clone", ".to_owned()")].as_slice(),
409 .and_then(|args| args.args.first())
410 .and_then(|arg| if let GenericArg::Type(ty) = arg {
417 cx.tcx.lang_items().slice_impl()
419 Some(sym::String) => (
420 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
422 cx.tcx.lang_items().str_impl()
424 Some(sym::PathBuf) => (
425 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
430 let ty_name = name.args
432 args.args.iter().find_map(|a| match a {
433 GenericArg::Type(x) => Some(x),
437 .and_then(|arg| snippet_opt(cx, arg.span))
438 .unwrap_or_else(|| substs.type_at(1).to_string());
443 "using a reference to `Cow` is not recommended",
445 format!("&{}{}", mutability.prefix_str(), ty_name),
446 Applicability::Unspecified,
456 ty_name: name.ident.name,
458 ref_prefix: RefPrefix {
463 deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))),
471 fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
472 if let FnRetTy::Return(ty) = decl.output {
473 if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
474 let mut immutables = vec![];
475 for (_, mutbl, argspan) in decl
478 .filter_map(get_rptr_lm)
479 .filter(|&(lt, _, _)| lt.name == out.name)
481 if mutbl == Mutability::Mut {
484 immutables.push(argspan);
486 if immutables.is_empty() {
493 "mutable borrow from immutable input(s)",
495 let ms = MultiSpan::from_spans(immutables);
496 diag.span_note(ms, "immutable borrow here");
503 #[allow(clippy::too_many_lines)]
504 fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
505 struct V<'cx, 'tcx> {
506 cx: &'cx LateContext<'tcx>,
507 /// Map from a local id to which argument it cam from (index into `Self::args` and
509 bindings: HirIdMap<usize>,
510 /// The arguments being checked.
511 args: &'cx [PtrArg<'tcx>],
512 /// The results for each argument (len should match args.len)
513 results: Vec<PtrArgResult>,
514 /// The number of arguments which can't be linted. Used to return early.
517 impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
518 type Map = Map<'tcx>;
519 fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
520 NestedVisitorMap::OnlyBodies(self.cx.tcx.hir())
523 fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
525 fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
526 if self.skip_count == self.args.len() {
530 // Check if this is local we care about
531 let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
533 None => return walk_expr(self, e),
535 let args = &self.args[args_idx];
536 let result = &mut self.results[args_idx];
538 // Helper function to handle early returns.
539 let mut set_skip_flag = || {
541 self.skip_count += 1;
546 match get_expr_use_or_unification_node(self.cx.tcx, e) {
547 Some((Node::Stmt(_), _)) => (),
548 Some((Node::Local(l), _)) => {
549 // Only trace simple bindings. e.g `let x = y;`
550 if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
551 self.bindings.insert(id, args_idx);
556 Some((Node::Expr(e), child_id)) => match e.kind {
557 ExprKind::Call(f, expr_args) => {
558 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
559 if expr_sig(self.cx, f)
560 .map(|sig| sig.input(i).skip_binder().peel_refs())
561 .map_or(true, |ty| match *ty.kind() {
562 ty::Param(_) => true,
563 ty::Adt(def, _) => def.did == args.ty_did,
567 // Passed to a function taking the non-dereferenced type.
571 ExprKind::MethodCall(name, _, expr_args @ [self_arg, ..], _) => {
572 let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
574 // Check if the method can be renamed.
575 let name = name.ident.as_str();
576 if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
577 result.replacements.push(PtrArgReplacement {
579 self_span: self_arg.span,
586 let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
593 match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
597 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
600 if def.did == args.ty_did
602 || self.cx.tcx.trait_of_item(id).is_some()
603 || !args.deref_assoc_items.map_or(false, |(id, items)| {
605 .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id)
614 // Indexing is fine for currently supported types.
615 ExprKind::Index(e, _) if e.hir_id == child_id => (),
616 _ => set_skip_flag(),
618 _ => set_skip_flag(),
623 let mut skip_count = 0;
624 let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
630 .filter_map(|(i, arg)| {
631 let param = &body.params[arg.idx];
632 match param.pat.kind {
633 PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
634 if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
640 results[arg.idx].skip = true;
650 v.visit_expr(&body.value);
654 fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
655 if let TyKind::Rptr(ref lt, ref m) = ty.kind {
656 Some((lt, m.mutbl, ty.span))
662 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
663 if let ExprKind::Call(pathexp, []) = expr.kind {
664 expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
665 match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()