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.
51 /// 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
77 /// if x == ptr::null {
88 #[clippy::version = "pre 1.29.0"]
91 "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead"
94 declare_clippy_lint
! {
96 /// This lint checks for functions that take immutable references and return
97 /// mutable ones. This will not trigger if no unsafe code exists as there
98 /// are multiple safe functions which will do this transformation
100 /// To be on the conservative side, if there's at least one mutable
101 /// reference with the output lifetime, this lint will not trigger.
103 /// ### Why is this bad?
104 /// Creating a mutable reference which can be repeatably derived from an
105 /// immutable reference is unsound as it allows creating multiple live
106 /// mutable references to the same object.
108 /// This [error](https://github.com/rust-lang/rust/issues/39465) actually
109 /// lead to an interim Rust release 1.15.1.
111 /// ### Known problems
112 /// This pattern is used by memory allocators to allow allocating multiple
113 /// objects while returning mutable references to each one. So long as
114 /// different mutable references are returned each time such a function may
119 /// fn foo(&Foo) -> &mut Bar { .. }
121 #[clippy::version = "pre 1.29.0"]
124 "fns that create mutable refs from immutable ref args"
127 declare_clippy_lint
! {
129 /// This lint checks for invalid usages of `ptr::null`.
131 /// ### Why is this bad?
132 /// This causes undefined behavior.
136 /// // Undefined behavior
137 /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); }
142 /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); }
144 #[clippy::version = "1.53.0"]
145 pub INVALID_NULL_PTR_USAGE
,
147 "invalid usage of a null pointer, suggesting `NonNull::dangling()` instead"
150 declare_lint_pass
!(Ptr
=> [PTR_ARG
, CMP_NULL
, MUT_FROM_REF
, INVALID_NULL_PTR_USAGE
]);
152 impl<'tcx
> LateLintPass
<'tcx
> for Ptr
{
153 fn check_trait_item(&mut self, cx
: &LateContext
<'tcx
>, item
: &'tcx TraitItem
<'_
>) {
154 if let TraitItemKind
::Fn(sig
, trait_method
) = &item
.kind
{
155 if matches
!(trait_method
, TraitFn
::Provided(_
)) {
156 // Handled by check body.
160 check_mut_from_ref(cx
, sig
, None
);
161 for arg
in check_fn_args(
163 cx
.tcx
.fn_sig(item
.def_id
).skip_binder().inputs(),
167 .filter(|arg
| arg
.mutability() == Mutability
::Not
)
175 format
!("{}{}", arg
.ref_prefix
, arg
.deref_ty
.display(cx
)),
176 Applicability
::Unspecified
,
182 fn check_body(&mut self, cx
: &LateContext
<'tcx
>, body
: &'tcx Body
<'_
>) {
183 let hir
= cx
.tcx
.hir();
184 let mut parents
= hir
.parent_iter(body
.value
.hir_id
);
185 let (item_id
, sig
, is_trait_item
) = match parents
.next() {
186 Some((_
, Node
::Item(i
))) => {
187 if let ItemKind
::Fn(sig
, ..) = &i
.kind
{
188 (i
.def_id
, sig
, false)
193 Some((_
, Node
::ImplItem(i
))) => {
194 if !matches
!(parents
.next(),
195 Some((_
, Node
::Item(i
))) if matches
!(&i
.kind
, ItemKind
::Impl(i
) if i
.of_trait
.is_none())
199 if let ImplItemKind
::Fn(sig
, _
) = &i
.kind
{
200 (i
.def_id
, sig
, false)
205 Some((_
, Node
::TraitItem(i
))) => {
206 if let TraitItemKind
::Fn(sig
, _
) = &i
.kind
{
207 (i
.def_id
, sig
, true)
215 check_mut_from_ref(cx
, sig
, Some(body
));
217 let sig
= cx
.tcx
.fn_sig(item_id
).skip_binder();
218 let lint_args
: Vec
<_
> = check_fn_args(cx
, sig
.inputs(), decl
.inputs
, body
.params
)
219 .filter(|arg
| !is_trait_item
|| arg
.mutability() == Mutability
::Not
)
221 let results
= check_ptr_arg_usage(cx
, body
, &lint_args
);
223 for (result
, args
) in results
.iter().zip(lint_args
.iter()).filter(|(r
, _
)| !r
.skip
) {
224 span_lint_and_then(cx
, PTR_ARG
, args
.span
, &args
.build_msg(), |diag
| {
225 diag
.multipart_suggestion(
227 iter
::once((args
.span
, format
!("{}{}", args
.ref_prefix
, args
.deref_ty
.display(cx
))))
228 .chain(result
.replacements
.iter().map(|r
| {
231 format
!("{}{}", snippet_opt(cx
, r
.self_span
).unwrap(), r
.replacement
),
235 Applicability
::Unspecified
,
241 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
242 if let ExprKind
::Binary(ref op
, l
, r
) = expr
.kind
{
243 if (op
.node
== BinOpKind
::Eq
|| op
.node
== BinOpKind
::Ne
) && (is_null_path(cx
, l
) || is_null_path(cx
, r
)) {
248 "comparing with null is better expressed by the `.is_null()` method",
252 check_invalid_ptr_usage(cx
, expr
);
257 fn check_invalid_ptr_usage
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
258 // (fn_path, arg_indices) - `arg_indices` are the `arg` positions where null would cause U.B.
259 const INVALID_NULL_PTR_USAGE_TABLE
: [(&[&str], &[usize]); 16] = [
260 (&paths
::SLICE_FROM_RAW_PARTS
, &[0]),
261 (&paths
::SLICE_FROM_RAW_PARTS_MUT
, &[0]),
262 (&paths
::PTR_COPY
, &[0, 1]),
263 (&paths
::PTR_COPY_NONOVERLAPPING
, &[0, 1]),
264 (&paths
::PTR_READ
, &[0]),
265 (&paths
::PTR_READ_UNALIGNED
, &[0]),
266 (&paths
::PTR_READ_VOLATILE
, &[0]),
267 (&paths
::PTR_REPLACE
, &[0]),
268 (&paths
::PTR_SLICE_FROM_RAW_PARTS
, &[0]),
269 (&paths
::PTR_SLICE_FROM_RAW_PARTS_MUT
, &[0]),
270 (&paths
::PTR_SWAP
, &[0, 1]),
271 (&paths
::PTR_SWAP_NONOVERLAPPING
, &[0, 1]),
272 (&paths
::PTR_WRITE
, &[0]),
273 (&paths
::PTR_WRITE_UNALIGNED
, &[0]),
274 (&paths
::PTR_WRITE_VOLATILE
, &[0]),
275 (&paths
::PTR_WRITE_BYTES
, &[0]),
279 if let ExprKind
::Call(fun
, args
) = expr
.kind
;
280 if let ExprKind
::Path(ref qpath
) = fun
.kind
;
281 if let Some(fun_def_id
) = cx
.qpath_res(qpath
, fun
.hir_id
).opt_def_id();
282 let fun_def_path
= cx
.get_def_path(fun_def_id
).into_iter().map(Symbol
::to_ident_string
).collect
::<Vec
<_
>>();
283 if let Some(&(_
, arg_indices
)) = INVALID_NULL_PTR_USAGE_TABLE
285 .find(|&&(fn_path
, _
)| fn_path
== fun_def_path
);
287 for &arg_idx
in arg_indices
{
288 if let Some(arg
) = args
.get(arg_idx
).filter(|arg
| is_null_path(cx
, arg
)) {
291 INVALID_NULL_PTR_USAGE
,
293 "pointer must be non-null",
295 "core::ptr::NonNull::dangling().as_ptr()".to_string(),
296 Applicability
::MachineApplicable
,
305 struct PtrArgResult
{
307 replacements
: Vec
<PtrArgReplacement
>,
310 struct PtrArgReplacement
{
313 replacement
: &'
static str,
316 struct PtrArg
<'tcx
> {
321 method_renames
: &'
static [(&'
static str, &'
static str)],
322 ref_prefix
: RefPrefix
,
323 deref_ty
: DerefTy
<'tcx
>,
326 fn build_msg(&self) -> String
{
328 "writing `&{}{}` instead of `&{}{}` involves a new object where a slice will do",
329 self.ref_prefix
.mutability
.prefix_str(),
331 self.ref_prefix
.mutability
.prefix_str(),
332 self.deref_ty
.argless_str(),
336 fn mutability(&self) -> Mutability
{
337 self.ref_prefix
.mutability
343 mutability
: Mutability
,
345 impl fmt
::Display
for RefPrefix
{
346 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
350 LifetimeName
::Param(_
, ParamName
::Plain(name
)) => {
354 LifetimeName
::Underscore
=> f
.write_str("'_ ")?
,
355 LifetimeName
::Static
=> f
.write_str("'static ")?
,
358 f
.write_str(self.mutability
.prefix_str())
362 struct DerefTyDisplay
<'a
, 'tcx
>(&'a LateContext
<'tcx
>, &'a DerefTy
<'tcx
>);
363 impl fmt
::Display
for DerefTyDisplay
<'_
, '_
> {
364 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
367 DerefTy
::Str
=> f
.write_str("str"),
368 DerefTy
::Path
=> f
.write_str("Path"),
369 DerefTy
::Slice(hir_ty
, ty
) => {
371 match hir_ty
.and_then(|s
| snippet_opt(self.0, s
)) {
372 Some(s
) => f
.write_str(&s
)?
,
384 Slice(Option
<Span
>, Ty
<'tcx
>),
386 impl<'tcx
> DerefTy
<'tcx
> {
387 fn argless_str(&self) -> &'
static str {
390 Self::Path
=> "Path",
391 Self::Slice(..) => "[_]",
395 fn display
<'a
>(&'a
self, cx
: &'a LateContext
<'tcx
>) -> DerefTyDisplay
<'a
, 'tcx
> {
396 DerefTyDisplay(cx
, self)
400 fn check_fn_args
<'cx
, 'tcx
: 'cx
>(
401 cx
: &'cx LateContext
<'tcx
>,
402 tys
: &'tcx
[Ty
<'tcx
>],
403 hir_tys
: &'tcx
[hir
::Ty
<'tcx
>],
404 params
: &'tcx
[Param
<'tcx
>],
405 ) -> impl Iterator
<Item
= PtrArg
<'tcx
>> + 'cx
{
409 .filter_map(|(i
, (ty
, hir_ty
))| {
411 if let ty
::Ref(_
, ty
, mutability
) = *ty
.kind();
412 if let ty
::Adt(adt
, substs
) = *ty
.kind();
414 if let TyKind
::Rptr(lt
, ref ty
) = hir_ty
.kind
;
415 if let TyKind
::Path(QPath
::Resolved(None
, path
)) = ty
.ty
.kind
;
417 // Check that the name as typed matches the actual name of the type.
418 // e.g. `fn foo(_: &Foo)` shouldn't trigger the lint when `Foo` is an alias for `Vec`
419 if let [.., name
] = path
.segments
;
420 if cx
.tcx
.item_name(adt
.did()) == name
.ident
.name
;
422 if !is_lint_allowed(cx
, PTR_ARG
, hir_ty
.hir_id
);
423 if params
.get(i
).map_or(true, |p
| !is_lint_allowed(cx
, PTR_ARG
, p
.hir_id
));
426 let (method_renames
, deref_ty
) = match cx
.tcx
.get_diagnostic_name(adt
.did()) {
428 [("clone", ".to_owned()")].as_slice(),
431 .and_then(|args
| args
.args
.first())
432 .and_then(|arg
| if let GenericArg
::Type(ty
) = arg
{
440 Some(sym
::String
) => (
441 [("clone", ".to_owned()"), ("as_str", "")].as_slice(),
444 Some(sym
::PathBuf
) => (
445 [("clone", ".to_path_buf()"), ("as_path", "")].as_slice(),
448 Some(sym
::Cow
) if mutability
== Mutability
::Not
=> {
449 let ty_name
= name
.args
451 args
.args
.iter().find_map(|a
| match a
{
452 GenericArg
::Type(x
) => Some(x
),
456 .and_then(|arg
| snippet_opt(cx
, arg
.span
))
457 .unwrap_or_else(|| substs
.type_at(1).to_string());
462 "using a reference to `Cow` is not recommended",
464 format
!("&{}{}", mutability
.prefix_str(), ty_name
),
465 Applicability
::Unspecified
,
475 ty_name
: name
.ident
.name
,
477 ref_prefix
: RefPrefix
{
489 fn check_mut_from_ref
<'tcx
>(cx
: &LateContext
<'tcx
>, sig
: &FnSig
<'_
>, body
: Option
<&'tcx Body
<'_
>>) {
490 if let FnRetTy
::Return(ty
) = sig
.decl
.output
491 && let Some((out
, Mutability
::Mut
, _
)) = get_rptr_lm(ty
)
493 let args
: Option
<Vec
<_
>> = sig
497 .filter_map(get_rptr_lm
)
498 .filter(|&(lt
, _
, _
)| lt
.name
== out
.name
)
499 .map(|(_
, mutability
, span
)| (mutability
== Mutability
::Not
).then(|| span
))
501 if let Some(args
) = args
503 && body
.map_or(true, |body
| {
504 sig
.header
.unsafety
== Unsafety
::Unsafe
|| contains_unsafe_block(cx
, &body
.value
)
511 "mutable borrow from immutable input(s)",
513 let ms
= MultiSpan
::from_spans(args
);
514 diag
.span_note(ms
, "immutable borrow here");
521 #[expect(clippy::too_many_lines)]
522 fn check_ptr_arg_usage
<'tcx
>(cx
: &LateContext
<'tcx
>, body
: &'tcx Body
<'_
>, args
: &[PtrArg
<'tcx
>]) -> Vec
<PtrArgResult
> {
523 struct V
<'cx
, 'tcx
> {
524 cx
: &'cx LateContext
<'tcx
>,
525 /// Map from a local id to which argument it came from (index into `Self::args` and
527 bindings
: HirIdMap
<usize>,
528 /// The arguments being checked.
529 args
: &'cx
[PtrArg
<'tcx
>],
530 /// The results for each argument (len should match args.len)
531 results
: Vec
<PtrArgResult
>,
532 /// The number of arguments which can't be linted. Used to return early.
535 impl<'tcx
> Visitor
<'tcx
> for V
<'_
, 'tcx
> {
536 type NestedFilter
= nested_filter
::OnlyBodies
;
537 fn nested_visit_map(&mut self) -> Self::Map
{
541 fn visit_anon_const(&mut self, _
: &'tcx AnonConst
) {}
543 fn visit_expr(&mut self, e
: &'tcx Expr
<'_
>) {
544 if self.skip_count
== self.args
.len() {
548 // Check if this is local we care about
549 let args_idx
= match path_to_local(e
).and_then(|id
| self.bindings
.get(&id
)) {
551 None
=> return walk_expr(self, e
),
553 let args
= &self.args
[args_idx
];
554 let result
= &mut self.results
[args_idx
];
556 // Helper function to handle early returns.
557 let mut set_skip_flag
= || {
559 self.skip_count
+= 1;
564 match get_expr_use_or_unification_node(self.cx
.tcx
, e
) {
565 Some((Node
::Stmt(_
), _
)) => (),
566 Some((Node
::Local(l
), _
)) => {
567 // Only trace simple bindings. e.g `let x = y;`
568 if let PatKind
::Binding(BindingAnnotation
::Unannotated
, id
, _
, None
) = l
.pat
.kind
{
569 self.bindings
.insert(id
, args_idx
);
574 Some((Node
::Expr(e
), child_id
)) => match e
.kind
{
575 ExprKind
::Call(f
, expr_args
) => {
576 let i
= expr_args
.iter().position(|arg
| arg
.hir_id
== child_id
).unwrap_or(0);
577 if expr_sig(self.cx
, f
)
578 .map(|sig
| sig
.input(i
).skip_binder().peel_refs())
579 .map_or(true, |ty
| match *ty
.kind() {
580 ty
::Param(_
) => true,
581 ty
::Adt(def
, _
) => def
.did() == args
.ty_did
,
585 // Passed to a function taking the non-dereferenced type.
589 ExprKind
::MethodCall(name
, expr_args @
[self_arg
, ..], _
) => {
590 let i
= expr_args
.iter().position(|arg
| arg
.hir_id
== child_id
).unwrap_or(0);
592 // Check if the method can be renamed.
593 let name
= name
.ident
.as_str();
594 if let Some((_
, replacement
)) = args
.method_renames
.iter().find(|&&(x
, _
)| x
== name
) {
595 result
.replacements
.push(PtrArgReplacement
{
597 self_span
: self_arg
.span
,
604 let id
= if let Some(x
) = self.cx
.typeck_results().type_dependent_def_id(e
.hir_id
) {
611 match *self.cx
.tcx
.fn_sig(id
).skip_binder().inputs()[i
].peel_refs().kind() {
615 // If the types match check for methods which exist on both types. e.g. `Vec::len` and
617 ty
::Adt(def
, _
) if def
.did() == args
.ty_did
=> {
623 // Indexing is fine for currently supported types.
624 ExprKind
::Index(e
, _
) if e
.hir_id
== child_id
=> (),
625 _
=> set_skip_flag(),
627 _
=> set_skip_flag(),
632 let mut skip_count
= 0;
633 let mut results
= args
.iter().map(|_
| PtrArgResult
::default()).collect
::<Vec
<_
>>();
639 .filter_map(|(i
, arg
)| {
640 let param
= &body
.params
[arg
.idx
];
641 match param
.pat
.kind
{
642 PatKind
::Binding(BindingAnnotation
::Unannotated
, id
, _
, None
)
643 if !is_lint_allowed(cx
, PTR_ARG
, param
.hir_id
) =>
649 results
[i
].skip
= true;
659 v
.visit_expr(&body
.value
);
663 fn get_rptr_lm
<'tcx
>(ty
: &'tcx hir
::Ty
<'tcx
>) -> Option
<(&'tcx Lifetime
, Mutability
, Span
)> {
664 if let TyKind
::Rptr(ref lt
, ref m
) = ty
.kind
{
665 Some((lt
, m
.mutbl
, ty
.span
))
671 fn is_null_path(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
672 if let ExprKind
::Call(pathexp
, []) = expr
.kind
{
673 path_def_id(cx
, pathexp
).map_or(false, |id
| {
674 matches
!(cx
.tcx
.get_diagnostic_name(id
), Some(sym
::ptr_null
| sym
::ptr_null_mut
))