]> git.lizzy.rs Git - rust.git/blobdiff - clippy_lints/src/ptr.rs
modify code
[rust.git] / clippy_lints / src / ptr.rs
index 12c44436874e1dbdff49142e105f4ef49275f82a..b5d65542de0bf1c068279149bb67df8615006a81 100644 (file)
@@ -1,56 +1,44 @@
 //! Checks for usage of  `&Vec[_]` and `&String`.
 
 use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
-use clippy_utils::ptr::get_spans;
 use clippy_utils::source::snippet_opt;
-use clippy_utils::ty::{is_type_diagnostic_item, match_type, walk_ptrs_hir_ty};
-use clippy_utils::{expr_path_res, is_allowed, match_any_def_paths, paths};
+use clippy_utils::ty::expr_sig;
+use clippy_utils::{
+    expr_path_res, get_expr_use_or_unification_node, is_lint_allowed, match_any_diagnostic_items, path_to_local, paths,
+};
 use if_chain::if_chain;
 use rustc_errors::Applicability;
+use rustc_hir::def_id::DefId;
+use rustc_hir::hir_id::HirIdMap;
+use rustc_hir::intravisit::{walk_expr, Visitor};
 use rustc_hir::{
-    BinOpKind, BodyId, Expr, ExprKind, FnDecl, FnRetTy, GenericArg, HirId, Impl, ImplItem, ImplItemKind, Item,
-    ItemKind, Lifetime, MutTy, Mutability, Node, PathSegment, QPath, TraitFn, TraitItem, TraitItemKind, Ty, TyKind,
+    self as hir, AnonConst, BinOpKind, BindingAnnotation, Body, Expr, ExprKind, FnDecl, FnRetTy, GenericArg,
+    ImplItemKind, ItemKind, Lifetime, LifetimeName, Mutability, Node, Param, ParamName, PatKind, QPath, TraitFn,
+    TraitItem, TraitItemKind, TyKind,
 };
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
+use rustc_middle::hir::nested_filter;
+use rustc_middle::ty::{self, AssocItems, AssocKind, Ty};
 use rustc_session::{declare_lint_pass, declare_tool_lint};
 use rustc_span::source_map::Span;
 use rustc_span::symbol::Symbol;
 use rustc_span::{sym, MultiSpan};
-use std::borrow::Cow;
+use std::fmt;
+use std::iter;
 
 declare_clippy_lint! {
-    /// **What it does:** This lint checks for function arguments of type `&String`
-    /// or `&Vec` unless the references are mutable. It will also suggest you
-    /// replace `.clone()` calls with the appropriate `.to_owned()`/`to_string()`
-    /// calls.
+    /// ### What it does
+    /// This lint checks for function arguments of type `&String`, `&Vec`,
+    /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls
+    /// with the appropriate `.to_owned()`/`to_string()` calls.
     ///
-    /// **Why is this bad?** Requiring the argument to be of the specific size
+    /// ### Why is this bad?
+    /// Requiring the argument to be of the specific size
     /// makes the function less useful for no benefit; slices in the form of `&[T]`
     /// or `&str` usually suffice and can be obtained from other types, too.
     ///
-    /// **Known problems:** The lint does not follow data. So if you have an
-    /// argument `x` and write `let y = x; y.clone()` the lint will not suggest
-    /// changing that `.clone()` to `.to_owned()`.
-    ///
-    /// Other functions called from this function taking a `&String` or `&Vec`
-    /// argument may also fail to compile if you change the argument. Applying
-    /// this lint on them will fix the problem, but they may be in other crates.
-    ///
-    /// One notable example of a function that may cause issues, and which cannot
-    /// easily be changed due to being in the standard library is `Vec::contains`.
-    /// when called on a `Vec<Vec<T>>`. If a `&Vec` is passed to that method then
-    /// it will compile, but if a `&[T]` is passed then it will not compile.
-    ///
-    /// ```ignore
-    /// fn cannot_take_a_slice(v: &Vec<u8>) -> bool {
-    ///     let vec_of_vecs: Vec<Vec<u8>> = some_other_fn();
-    ///
-    ///     vec_of_vecs.contains(v)
-    /// }
-    /// ```
-    ///
-    /// Also there may be `fn(&Vec)`-typed references pointing to your function.
+    /// ### Known problems
+    /// There may be `fn(&Vec)`-typed references pointing to your function.
     /// If you have them, you will get a compiler error after applying this lint's
     /// suggestions. You then have the choice to undo your changes or change the
     /// type of the reference.
@@ -59,7 +47,7 @@
     /// other crates referencing it, of which you may not be aware. Carefully
     /// deprecate the function before applying the lint suggestions in this case.
     ///
-    /// **Example:**
+    /// ### Example
     /// ```ignore
     /// // Bad
     /// fn foo(&Vec<u32>) { .. }
     /// // Good
     /// fn foo(&[u32]) { .. }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub PTR_ARG,
     style,
     "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively"
 }
 
 declare_clippy_lint! {
-    /// **What it does:** This lint checks for equality comparisons with `ptr::null`
+    /// ### What it does
+    /// This lint checks for equality comparisons with `ptr::null`
     ///
-    /// **Why is this bad?** It's easier and more readable to use the inherent
+    /// ### Why is this bad?
+    /// It's easier and more readable to use the inherent
     /// `.is_null()`
     /// method instead
     ///
-    /// **Known problems:** None.
-    ///
-    /// **Example:**
+    /// ### Example
     /// ```ignore
     /// // Bad
     /// if x == ptr::null {
     ///     ..
     /// }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub CMP_NULL,
     style,
     "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
 }
 
 declare_clippy_lint! {
-    /// **What it does:** This lint checks for functions that take immutable
+    /// ### What it does
+    /// This lint checks for functions that take immutable
     /// references and return mutable ones.
     ///
-    /// **Why is this bad?** This is trivially unsound, as one can create two
+    /// ### Why is this bad?
+    /// This is trivially unsound, as one can create two
     /// mutable references from the same (immutable!) source.
     /// This [error](https://github.com/rust-lang/rust/issues/39465)
     /// actually lead to an interim Rust release 1.15.1.
     ///
-    /// **Known problems:** To be on the conservative side, if there's at least one
+    /// ### Known problems
+    /// To be on the conservative side, if there's at least one
     /// mutable reference with the output lifetime, this lint will not trigger.
     /// In practice, this case is unlikely anyway.
     ///
-    /// **Example:**
+    /// ### Example
     /// ```ignore
     /// fn foo(&Foo) -> &mut Bar { .. }
     /// ```
+    #[clippy::version = "pre 1.29.0"]
     pub MUT_FROM_REF,
     correctness,
     "fns that create mutable refs from immutable ref args"
 }
 
 declare_clippy_lint! {
-    /// **What it does:** This lint checks for invalid usages of `ptr::null`.
+    /// ### What it does
+    /// This lint checks for invalid usages of `ptr::null`.
     ///
-    /// **Why is this bad?** This causes undefined behavior.
+    /// ### Why is this bad?
+    /// This causes undefined behavior.
     ///
-    /// **Known problems:** None.
-    ///
-    /// **Example:**
+    /// ### Example
     /// ```ignore
     /// // Bad. Undefined behavior
     /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
     /// ```
     ///
+    /// ```ignore
     /// // Good
     /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
     /// ```
+    #[clippy::version = "1.53.0"]
     pub INVALID_NULL_PTR_USAGE,
     correctness,
     "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
 declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, INVALID_NULL_PTR_USAGE]);
 
 impl<'tcx> LateLintPass<'tcx> for Ptr {
-    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
-        if let ItemKind::Fn(ref sig, _, body_id) = item.kind {
-            check_fn(cx, sig.decl, item.hir_id(), Some(body_id));
-        }
-    }
+    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
+        if let TraitItemKind::Fn(sig, trait_method) = &item.kind {
+            if matches!(trait_method, TraitFn::Provided(_)) {
+                // Handled by check body.
+                return;
+            }
 
-    fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx ImplItem<'_>) {
-        if let ImplItemKind::Fn(ref sig, body_id) = item.kind {
-            let parent_item = cx.tcx.hir().get_parent_item(item.hir_id());
-            if let Some(Node::Item(it)) = cx.tcx.hir().find(parent_item) {
-                if let ItemKind::Impl(Impl { of_trait: Some(_), .. }) = it.kind {
-                    return; // ignore trait impls
-                }
+            check_mut_from_ref(cx, sig.decl);
+            for arg in check_fn_args(
+                cx,
+                cx.tcx.fn_sig(item.def_id).skip_binder().inputs(),
+                sig.decl.inputs,
+                &[],
+            ) {
+                span_lint_and_sugg(
+                    cx,
+                    PTR_ARG,
+                    arg.span,
+                    &arg.build_msg(),
+                    "change this to",
+                    format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)),
+                    Applicability::Unspecified,
+                );
             }
-            check_fn(cx, sig.decl, item.hir_id(), Some(body_id));
         }
     }
 
-    fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) {
-        if let TraitItemKind::Fn(ref sig, ref trait_method) = item.kind {
-            let body_id = if let TraitFn::Provided(b) = *trait_method {
-                Some(b)
-            } else {
-                None
-            };
-            check_fn(cx, sig.decl, item.hir_id(), body_id);
+    fn check_body(&mut self, cx: &LateContext<'tcx>, body: &'tcx Body<'_>) {
+        let hir = cx.tcx.hir();
+        let mut parents = hir.parent_iter(body.value.hir_id);
+        let (item_id, decl) = match parents.next() {
+            Some((_, Node::Item(i))) => {
+                if let ItemKind::Fn(sig, ..) = &i.kind {
+                    (i.def_id, sig.decl)
+                } else {
+                    return;
+                }
+            },
+            Some((_, Node::ImplItem(i))) => {
+                if !matches!(parents.next(),
+                    Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none())
+                ) {
+                    return;
+                }
+                if let ImplItemKind::Fn(sig, _) = &i.kind {
+                    (i.def_id, sig.decl)
+                } else {
+                    return;
+                }
+            },
+            Some((_, Node::TraitItem(i))) => {
+                if let TraitItemKind::Fn(sig, _) = &i.kind {
+                    (i.def_id, sig.decl)
+                } else {
+                    return;
+                }
+            },
+            _ => return,
+        };
+
+        check_mut_from_ref(cx, decl);
+        let sig = cx.tcx.fn_sig(item_id).skip_binder();
+        let lint_args: Vec<_> = check_fn_args(cx, sig.inputs(), decl.inputs, body.params).collect();
+        let results = check_ptr_arg_usage(cx, body, &lint_args);
+
+        for (result, args) in results.iter().zip(lint_args.iter()).filter(|(r, _)| !r.skip) {
+            span_lint_and_then(cx, PTR_ARG, args.span, &args.build_msg(), |diag| {
+                diag.multipart_suggestion(
+                    "change this to",
+                    iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx))))
+                        .chain(result.replacements.iter().map(|r| {
+                            (
+                                r.expr_span,
+                                format!("{}{}", snippet_opt(cx, r.self_span).unwrap(), r.replacement),
+                            )
+                        }))
+                        .collect(),
+                    Applicability::Unspecified,
+                );
+            });
         }
     }
 
@@ -236,143 +286,206 @@ fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
     }
 }
 
-#[allow(clippy::too_many_lines)]
-fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id: Option<BodyId>) {
-    let fn_def_id = cx.tcx.hir().local_def_id(fn_id);
-    let sig = cx.tcx.fn_sig(fn_def_id);
-    let fn_ty = sig.skip_binder();
-    let body = opt_body_id.map(|id| cx.tcx.hir().body(id));
-
-    for (idx, (arg, ty)) in decl.inputs.iter().zip(fn_ty.inputs()).enumerate() {
-        // Honor the allow attribute on parameters. See issue 5644.
-        if let Some(body) = &body {
-            if is_allowed(cx, PTR_ARG, body.params[idx].hir_id) {
-                continue;
-            }
+#[derive(Default)]
+struct PtrArgResult {
+    skip: bool,
+    replacements: Vec<PtrArgReplacement>,
+}
+
+struct PtrArgReplacement {
+    expr_span: Span,
+    self_span: Span,
+    replacement: &'static str,
+}
+
+struct PtrArg<'tcx> {
+    idx: usize,
+    span: Span,
+    ty_did: DefId,
+    ty_name: Symbol,
+    method_renames: &'static [(&'static str, &'static str)],
+    ref_prefix: RefPrefix,
+    deref_ty: DerefTy<'tcx>,
+    deref_assoc_items: Option<(DefId, &'tcx AssocItems<'tcx>)>,
+}
+impl PtrArg<'_> {
+    fn build_msg(&self) -> String {
+        format!(
+            "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
+            self.ref_prefix.mutability.prefix_str(),
+            self.ty_name,
+            self.ref_prefix.mutability.prefix_str(),
+            self.deref_ty.argless_str(),
+        )
+    }
+}
+
+struct RefPrefix {
+    lt: LifetimeName,
+    mutability: Mutability,
+}
+impl fmt::Display for RefPrefix {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use fmt::Write;
+        f.write_char('&')?;
+        match self.lt {
+            LifetimeName::Param(ParamName::Plain(name)) => {
+                name.fmt(f)?;
+                f.write_char(' ')?;
+            },
+            LifetimeName::Underscore => f.write_str("'_ ")?,
+            LifetimeName::Static => f.write_str("'static ")?,
+            _ => (),
         }
+        f.write_str(self.mutability.prefix_str())
+    }
+}
 
-        if let ty::Ref(_, ty, Mutability::Not) = ty.kind() {
-            if is_type_diagnostic_item(cx, ty, sym::vec_type) {
-                if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_owned()")]) {
-                    span_lint_and_then(
-                        cx,
-                        PTR_ARG,
-                        arg.span,
-                        "writing `&Vec<_>` instead of `&[_]` involves one more reference and cannot be used \
-                         with non-Vec-based slices",
-                        |diag| {
-                            if let Some(ref snippet) = get_only_generic_arg_snippet(cx, arg) {
-                                diag.span_suggestion(
-                                    arg.span,
-                                    "change this to",
-                                    format!("&[{}]", snippet),
-                                    Applicability::Unspecified,
-                                );
-                            }
-                            for (clonespan, suggestion) in spans {
-                                diag.span_suggestion(
-                                    clonespan,
-                                    &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
-                                        Cow::Owned(format!("change `{}` to", x))
-                                    }),
-                                    suggestion.into(),
-                                    Applicability::Unspecified,
-                                );
-                            }
-                        },
-                    );
+struct DerefTyDisplay<'a, 'tcx>(&'a LateContext<'tcx>, &'a DerefTy<'tcx>);
+impl fmt::Display for DerefTyDisplay<'_, '_> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        use std::fmt::Write;
+        match self.1 {
+            DerefTy::Str => f.write_str("str"),
+            DerefTy::Path => f.write_str("Path"),
+            DerefTy::Slice(hir_ty, ty) => {
+                f.write_char('[')?;
+                match hir_ty.and_then(|s| snippet_opt(self.0, s)) {
+                    Some(s) => f.write_str(&s)?,
+                    None => ty.fmt(f)?,
                 }
-            } else if is_type_diagnostic_item(cx, ty, sym::string_type) {
-                if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_string()"), ("as_str", "")]) {
-                    span_lint_and_then(
-                        cx,
-                        PTR_ARG,
-                        arg.span,
-                        "writing `&String` instead of `&str` involves a new object where a slice will do",
-                        |diag| {
-                            diag.span_suggestion(arg.span, "change this to", "&str".into(), Applicability::Unspecified);
-                            for (clonespan, suggestion) in spans {
-                                diag.span_suggestion_short(
-                                    clonespan,
-                                    &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
-                                        Cow::Owned(format!("change `{}` to", x))
+                f.write_char(']')
+            },
+        }
+    }
+}
+
+enum DerefTy<'tcx> {
+    Str,
+    Path,
+    Slice(Option<Span>, Ty<'tcx>),
+}
+impl<'tcx> DerefTy<'tcx> {
+    fn argless_str(&self) -> &'static str {
+        match *self {
+            Self::Str => "str",
+            Self::Path => "Path",
+            Self::Slice(..) => "[_]",
+        }
+    }
+
+    fn display<'a>(&'a self, cx: &'a LateContext<'tcx>) -> DerefTyDisplay<'a, 'tcx> {
+        DerefTyDisplay(cx, self)
+    }
+}
+
+fn check_fn_args<'cx, 'tcx: 'cx>(
+    cx: &'cx LateContext<'tcx>,
+    tys: &'tcx [Ty<'_>],
+    hir_tys: &'tcx [hir::Ty<'_>],
+    params: &'tcx [Param<'_>],
+) -> impl Iterator<Item = PtrArg<'tcx>> + 'cx {
+    tys.iter()
+        .zip(hir_tys.iter())
+        .enumerate()
+        .filter_map(|(i, (ty, hir_ty))| {
+            if_chain! {
+                if let ty::Ref(_, ty, mutability) = *ty.kind();
+                if let ty::Adt(adt, substs) = *ty.kind();
+
+                if let TyKind::Rptr(lt, ref ty) = hir_ty.kind;
+                if let TyKind::Path(QPath::Resolved(None, path)) = ty.ty.kind;
+
+                // Check that the name as typed matches the actual name of the type.
+                // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
+                if let [.., name] = path.segments;
+                if cx.tcx.item_name(adt.did) == name.ident.name;
+
+                if !is_lint_allowed(cx, PTR_ARG, hir_ty.hir_id);
+                if params.get(i).map_or(true, |p| !is_lint_allowed(cx, PTR_ARG, p.hir_id));
+
+                then {
+                    let (method_renames, deref_ty, deref_impl_id) = match cx.tcx.get_diagnostic_name(adt.did) {
+                        Some(sym::Vec) => (
+                            [("clone", ".to_owned()")].as_slice(),
+                            DerefTy::Slice(
+                                name.args
+                                    .and_then(|args| args.args.first())
+                                    .and_then(|arg| if let GenericArg::Type(ty) = arg {
+                                        Some(ty.span)
+                                    } else {
+                                        None
                                     }),
-                                    suggestion.into(),
-                                    Applicability::Unspecified,
-                                );
-                            }
-                        },
-                    );
-                }
-            } else if is_type_diagnostic_item(cx, ty, sym::PathBuf) {
-                if let Some(spans) = get_spans(cx, opt_body_id, idx, &[("clone", ".to_path_buf()"), ("as_path", "")]) {
-                    span_lint_and_then(
-                        cx,
-                        PTR_ARG,
-                        arg.span,
-                        "writing `&PathBuf` instead of `&Path` involves a new object where a slice will do",
-                        |diag| {
-                            diag.span_suggestion(
-                                arg.span,
+                                substs.type_at(0),
+                            ),
+                            cx.tcx.lang_items().slice_impl()
+                        ),
+                        Some(sym::String) => (
+                            [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
+                            DerefTy::Str,
+                            cx.tcx.lang_items().str_impl()
+                        ),
+                        Some(sym::PathBuf) => (
+                            [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
+                            DerefTy::Path,
+                            None,
+                        ),
+                        Some(sym::Cow) => {
+                            let ty_name = name.args
+                                .and_then(|args| {
+                                    args.args.iter().find_map(|a| match a {
+                                        GenericArg::Type(x) => Some(x),
+                                        _ => None,
+                                    })
+                                })
+                                .and_then(|arg| snippet_opt(cx, arg.span))
+                                .unwrap_or_else(|| substs.type_at(1).to_string());
+                            span_lint_and_sugg(
+                                cx,
+                                PTR_ARG,
+                                hir_ty.span,
+                                "using a reference to `Cow` is not recommended",
                                 "change this to",
-                                "&Path".into(),
+                                format!("&{}{}", mutability.prefix_str(), ty_name),
                                 Applicability::Unspecified,
                             );
-                            for (clonespan, suggestion) in spans {
-                                diag.span_suggestion_short(
-                                    clonespan,
-                                    &snippet_opt(cx, clonespan).map_or("change the call to".into(), |x| {
-                                        Cow::Owned(format!("change `{}` to", x))
-                                    }),
-                                    suggestion.into(),
-                                    Applicability::Unspecified,
-                                );
-                            }
+                            return None;
                         },
-                    );
-                }
-            } else if match_type(cx, ty, &paths::COW) {
-                if_chain! {
-                    if let TyKind::Rptr(_, MutTy { ty, ..} ) = arg.kind;
-                    if let TyKind::Path(QPath::Resolved(None, pp)) = ty.kind;
-                    if let [ref bx] = *pp.segments;
-                    if let Some(params) = bx.args;
-                    if !params.parenthesized;
-                    if let Some(inner) = params.args.iter().find_map(|arg| match arg {
-                        GenericArg::Type(ty) => Some(ty),
-                        _ => None,
+                        _ => return None,
+                    };
+                    return Some(PtrArg {
+                        idx: i,
+                        span: hir_ty.span,
+                        ty_did: adt.did,
+                        ty_name: name.ident.name,
+                        method_renames,
+                        ref_prefix: RefPrefix {
+                            lt: lt.name,
+                            mutability,
+                        },
+                        deref_ty,
+                        deref_assoc_items: deref_impl_id.map(|id| (id, cx.tcx.associated_items(id))),
                     });
-                    let replacement = snippet_opt(cx, inner.span);
-                    if let Some(r) = replacement;
-                    then {
-                        span_lint_and_sugg(
-                            cx,
-                            PTR_ARG,
-                            arg.span,
-                            "using a reference to `Cow` is not recommended",
-                            "change this to",
-                            "&".to_owned() + &r,
-                            Applicability::Unspecified,
-                        );
-                    }
                 }
             }
-        }
-    }
+            None
+        })
+}
 
+fn check_mut_from_ref(cx: &LateContext<'_>, decl: &FnDecl<'_>) {
     if let FnRetTy::Return(ty) = decl.output {
         if let Some((out, Mutability::Mut, _)) = get_rptr_lm(ty) {
             let mut immutables = vec![];
-            for (_, ref mutbl, ref argspan) in decl
+            for (_, mutbl, argspan) in decl
                 .inputs
                 .iter()
-                .filter_map(|ty| get_rptr_lm(ty))
+                .filter_map(get_rptr_lm)
                 .filter(|&(lt, _, _)| lt.name == out.name)
             {
-                if *mutbl == Mutability::Mut {
+                if mutbl == Mutability::Mut {
                     return;
                 }
-                immutables.push(*argspan);
+                immutables.push(argspan);
             }
             if immutables.is_empty() {
                 return;
@@ -391,24 +504,158 @@ fn check_fn(cx: &LateContext<'_>, decl: &FnDecl<'_>, fn_id: HirId, opt_body_id:
     }
 }
 
-fn get_only_generic_arg_snippet(cx: &LateContext<'_>, arg: &Ty<'_>) -> Option<String> {
-    if_chain! {
-        if let TyKind::Path(QPath::Resolved(_, path)) = walk_ptrs_hir_ty(arg).kind;
-        if let Some(&PathSegment{args: Some(parameters), ..}) = path.segments.last();
-        let types: Vec<_> = parameters.args.iter().filter_map(|arg| match arg {
-            GenericArg::Type(ty) => Some(ty),
-            _ => None,
-        }).collect();
-        if types.len() == 1;
-        then {
-            snippet_opt(cx, types[0].span)
-        } else {
-            None
+#[allow(clippy::too_many_lines)]
+fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &'tcx Body<'_>, args: &[PtrArg<'tcx>]) -> Vec<PtrArgResult> {
+    struct V<'cx, 'tcx> {
+        cx: &'cx LateContext<'tcx>,
+        /// Map from a local id to which argument it came from (index into `Self::args` and
+        /// `Self::results`)
+        bindings: HirIdMap<usize>,
+        /// The arguments being checked.
+        args: &'cx [PtrArg<'tcx>],
+        /// The results for each argument (len should match args.len)
+        results: Vec<PtrArgResult>,
+        /// The number of arguments which can't be linted. Used to return early.
+        skip_count: usize,
+    }
+    impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> {
+        type NestedFilter = nested_filter::OnlyBodies;
+        fn nested_visit_map(&mut self) -> Self::Map {
+            self.cx.tcx.hir()
+        }
+
+        fn visit_anon_const(&mut self, _: &'tcx AnonConst) {}
+
+        fn visit_expr(&mut self, e: &'tcx Expr<'_>) {
+            if self.skip_count == self.args.len() {
+                return;
+            }
+
+            // Check if this is local we care about
+            let args_idx = match path_to_local(e).and_then(|id| self.bindings.get(&id)) {
+                Some(&i) => i,
+                None => return walk_expr(self, e),
+            };
+            let args = &self.args[args_idx];
+            let result = &mut self.results[args_idx];
+
+            // Helper function to handle early returns.
+            let mut set_skip_flag = || {
+                if result.skip {
+                    self.skip_count += 1;
+                }
+                result.skip = true;
+            };
+
+            match get_expr_use_or_unification_node(self.cx.tcx, e) {
+                Some((Node::Stmt(_), _)) => (),
+                Some((Node::Local(l), _)) => {
+                    // Only trace simple bindings. e.g `let x = y;`
+                    if let PatKind::Binding(BindingAnnotation::Unannotated, id, _, None) = l.pat.kind {
+                        self.bindings.insert(id, args_idx);
+                    } else {
+                        set_skip_flag();
+                    }
+                },
+                Some((Node::Expr(e), child_id)) => match e.kind {
+                    ExprKind::Call(f, expr_args) => {
+                        let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
+                        if expr_sig(self.cx, f)
+                            .map(|sig| sig.input(i).skip_binder().peel_refs())
+                            .map_or(true, |ty| match *ty.kind() {
+                                ty::Param(_) => true,
+                                ty::Adt(def, _) => def.did == args.ty_did,
+                                _ => false,
+                            })
+                        {
+                            // Passed to a function taking the non-dereferenced type.
+                            set_skip_flag();
+                        }
+                    },
+                    ExprKind::MethodCall(name, expr_args @ [self_arg, ..], _) => {
+                        let i = expr_args.iter().position(|arg| arg.hir_id == child_id).unwrap_or(0);
+                        if i == 0 {
+                            // Check if the method can be renamed.
+                            let name = name.ident.as_str();
+                            if let Some((_, replacement)) = args.method_renames.iter().find(|&&(x, _)| x == name) {
+                                result.replacements.push(PtrArgReplacement {
+                                    expr_span: e.span,
+                                    self_span: self_arg.span,
+                                    replacement,
+                                });
+                                return;
+                            }
+                        }
+
+                        let id = if let Some(x) = self.cx.typeck_results().type_dependent_def_id(e.hir_id) {
+                            x
+                        } else {
+                            set_skip_flag();
+                            return;
+                        };
+
+                        match *self.cx.tcx.fn_sig(id).skip_binder().inputs()[i].peel_refs().kind() {
+                            ty::Param(_) => {
+                                set_skip_flag();
+                            },
+                            // If the types match check for methods which exist on both types. e.g. `Vec::len` and
+                            // `slice::len`
+                            ty::Adt(def, _)
+                                if def.did == args.ty_did
+                                    && (i != 0
+                                        || self.cx.tcx.trait_of_item(id).is_some()
+                                        || !args.deref_assoc_items.map_or(false, |(id, items)| {
+                                            items
+                                                .find_by_name_and_kind(self.cx.tcx, name.ident, AssocKind::Fn, id)
+                                                .is_some()
+                                        })) =>
+                            {
+                                set_skip_flag();
+                            },
+                            _ => (),
+                        }
+                    },
+                    // Indexing is fine for currently supported types.
+                    ExprKind::Index(e, _) if e.hir_id == child_id => (),
+                    _ => set_skip_flag(),
+                },
+                _ => set_skip_flag(),
+            }
         }
     }
+
+    let mut skip_count = 0;
+    let mut results = args.iter().map(|_| PtrArgResult::default()).collect::<Vec<_>>();
+    let mut v = V {
+        cx,
+        bindings: args
+            .iter()
+            .enumerate()
+            .filter_map(|(i, arg)| {
+                let param = &body.params[arg.idx];
+                match param.pat.kind {
+                    PatKind::Binding(BindingAnnotation::Unannotated, id, _, None)
+                        if !is_lint_allowed(cx, PTR_ARG, param.hir_id) =>
+                    {
+                        Some((id, i))
+                    },
+                    _ => {
+                        skip_count += 1;
+                        results[arg.idx].skip = true;
+                        None
+                    },
+                }
+            })
+            .collect(),
+        args,
+        results,
+        skip_count,
+    };
+    v.visit_expr(&body.value);
+    v.results
 }
 
-fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
+fn get_rptr_lm<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability, Span)> {
     if let TyKind::Rptr(ref lt, ref m) = ty.kind {
         Some((lt, m.mutbl, ty.span))
     } else {
@@ -419,7 +666,7 @@ fn get_rptr_lm<'tcx>(ty: &'tcx Ty<'tcx>) -> Option<(&'tcx Lifetime, Mutability,
 fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
     if let ExprKind::Call(pathexp, []) = expr.kind {
         expr_path_res(cx, pathexp).opt_def_id().map_or(false, |id| {
-            match_any_def_paths(cx, id, &[&paths::PTR_NULL, &paths::PTR_NULL_MUT]).is_some()
+            match_any_diagnostic_items(cx, id, &[sym::ptr_null, sym::ptr_null_mut]).is_some()
         })
     } else {
         false