about summary refs log tree commit diff
path: root/clippy_lints
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2022-06-28 07:03:57 +0000
committerbors <bors@rust-lang.org>2022-06-28 07:03:57 +0000
commit373bb573af57a65a233fa30bb1623512b5c831c3 (patch)
tree2d11a04a15537babc1ba3e29428267cb98b3d7f8 /clippy_lints
parentd85539571ffa3befbbdd6b2933a79d31c28b42a0 (diff)
parentc10101cf1c62e504da6378ebf969cec2b74dccfb (diff)
downloadrust-373bb573af57a65a233fa30bb1623512b5c831c3.tar.gz
rust-373bb573af57a65a233fa30bb1623512b5c831c3.zip
Auto merge of #8639 - Jarcho:trivially_copy_pass_by_ref_5953, r=dswij
`trivially_copy_pass_by_ref` fixes

fixes #5953
fixes #2961

The fix for #5953 is overly aggressive, but the suggestion is so bad that it's worth the false negatives. Basically three things together:
* It's not obviously wrong
* It compiles
* It may actually work when tested

changelog: Don't lint `trivially_copy_pass_by_ref` when unsafe pointers are used.
changelog: Better track lifetimes when linting `trivially_copy_pass_by_ref`.
Diffstat (limited to 'clippy_lints')
-rw-r--r--clippy_lints/src/lib.rs1
-rw-r--r--clippy_lints/src/pass_by_ref_or_value.rs104
2 files changed, 68 insertions, 37 deletions
diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs
index db52122aef6..832e9df9665 100644
--- a/clippy_lints/src/lib.rs
+++ b/clippy_lints/src/lib.rs
@@ -7,6 +7,7 @@
 #![feature(let_chains)]
 #![feature(let_else)]
 #![feature(lint_reasons)]
+#![feature(never_type)]
 #![feature(once_cell)]
 #![feature(rustc_private)]
 #![feature(stmt_expr_attributes)]
diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs
index 05ab62786f4..5fa4fd74853 100644
--- a/clippy_lints/src/pass_by_ref_or_value.rs
+++ b/clippy_lints/src/pass_by_ref_or_value.rs
@@ -3,17 +3,20 @@ use std::iter;
 
 use clippy_utils::diagnostics::span_lint_and_sugg;
 use clippy_utils::source::snippet;
-use clippy_utils::ty::is_copy;
+use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
 use clippy_utils::{is_self, is_self_ty};
+use core::ops::ControlFlow;
 use if_chain::if_chain;
 use rustc_ast::attr;
+use rustc_data_structures::fx::FxHashSet;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::intravisit::FnKind;
 use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
 use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty;
+use rustc_middle::ty::adjustment::{Adjust, PointerCast};
 use rustc_middle::ty::layout::LayoutOf;
+use rustc_middle::ty::{self, RegionKind};
 use rustc_session::{declare_tool_lint, impl_lint_pass};
 use rustc_span::def_id::LocalDefId;
 use rustc_span::{sym, Span};
@@ -141,50 +144,76 @@ impl<'tcx> PassByRefOrValue {
         }
 
         let fn_sig = cx.tcx.fn_sig(def_id);
-        let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
-
         let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
 
-        for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
+        // Gather all the lifetimes found in the output type which may affect whether
+        // `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
+        let mut output_regions = FxHashSet::default();
+        for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
+            output_regions.insert(region);
+            ControlFlow::Continue(())
+        });
+
+        for (index, (input, ty)) in iter::zip(
+            decl.inputs,
+            fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
+        )
+        .enumerate()
+        {
             // All spans generated from a proc-macro invocation are the same...
             match span {
-                Some(s) if s == input.span => return,
+                Some(s) if s == input.span => continue,
                 _ => (),
             }
 
-            match ty.kind() {
-                ty::Ref(input_lt, ty, Mutability::Not) => {
-                    // Use lifetimes to determine if we're returning a reference to the
-                    // argument. In that case we can't switch to pass-by-value as the
-                    // argument will not live long enough.
-                    let output_lts = match *fn_sig.output().kind() {
-                        ty::Ref(output_lt, _, _) => vec![output_lt],
-                        ty::Adt(_, substs) => substs.regions().collect(),
-                        _ => vec![],
-                    };
+            match *ty.skip_binder().kind() {
+                ty::Ref(lt, ty, Mutability::Not) => {
+                    match lt.kind() {
+                        RegionKind::ReLateBound(index, region)
+                            if index.as_u32() == 0 && output_regions.contains(&region) =>
+                        {
+                            continue;
+                        },
+                        // Early bound regions on functions are either from the containing item, are bounded by another
+                        // lifetime, or are used as a bound for a type or lifetime.
+                        RegionKind::ReEarlyBound(..) => continue,
+                        _ => (),
+                    }
 
-                    if_chain! {
-                        if !output_lts.contains(input_lt);
-                        if is_copy(cx, *ty);
-                        if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
-                        if size <= self.ref_min_size;
-                        if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
-                        then {
-                            let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
-                                "self".into()
-                            } else {
-                                snippet(cx, decl_ty.span, "_").into()
-                            };
-                            span_lint_and_sugg(
-                                cx,
-                                TRIVIALLY_COPY_PASS_BY_REF,
-                                input.span,
-                                &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
-                                "consider passing by value instead",
-                                value_type,
-                                Applicability::Unspecified,
-                            );
+                    let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
+                    if is_copy(cx, ty)
+                        && let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
+                        && size <= self.ref_min_size
+                        && let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
+                    {
+                        if let Some(typeck) = cx.maybe_typeck_results() {
+                            // Don't lint if an unsafe pointer is created.
+                            // TODO: Limit the check only to unsafe pointers to the argument (or part of the argument)
+                            //       which escape the current function.
+                            if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr())
+                                || typeck
+                                    .adjustments()
+                                    .iter()
+                                    .flat_map(|(_, a)| a)
+                                    .any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer)))
+                            {
+                                continue;
+                            }
                         }
+                        let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
+                            "self".into()
+                        } else {
+                            snippet(cx, decl_ty.span, "_").into()
+                        };
+                        span_lint_and_sugg(
+                            cx,
+                            TRIVIALLY_COPY_PASS_BY_REF,
+                            input.span,
+                            &format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
+                            "consider passing by value instead",
+                            value_type,
+                            Applicability::Unspecified,
+                        );
                     }
                 },
 
@@ -196,6 +225,7 @@ impl<'tcx> PassByRefOrValue {
                             _ => continue,
                         }
                     }
+                    let ty = cx.tcx.erase_late_bound_regions(ty);
 
                     if_chain! {
                         if is_copy(cx, ty);