Skip to content

Commit 373bb57

Browse files
committed
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`.
2 parents d855395 + c10101c commit 373bb57

File tree

5 files changed

+143
-40
lines changed

5 files changed

+143
-40
lines changed

clippy_lints/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#![feature(let_chains)]
88
#![feature(let_else)]
99
#![feature(lint_reasons)]
10+
#![feature(never_type)]
1011
#![feature(once_cell)]
1112
#![feature(rustc_private)]
1213
#![feature(stmt_expr_attributes)]

clippy_lints/src/pass_by_ref_or_value.rs

+67-37
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@ use std::iter;
33

44
use clippy_utils::diagnostics::span_lint_and_sugg;
55
use clippy_utils::source::snippet;
6-
use clippy_utils::ty::is_copy;
6+
use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy};
77
use clippy_utils::{is_self, is_self_ty};
8+
use core::ops::ControlFlow;
89
use if_chain::if_chain;
910
use rustc_ast::attr;
11+
use rustc_data_structures::fx::FxHashSet;
1012
use rustc_errors::Applicability;
1113
use rustc_hir as hir;
1214
use rustc_hir::intravisit::FnKind;
1315
use rustc_hir::{BindingAnnotation, Body, FnDecl, HirId, Impl, ItemKind, MutTy, Mutability, Node, PatKind};
1416
use rustc_lint::{LateContext, LateLintPass};
15-
use rustc_middle::ty;
17+
use rustc_middle::ty::adjustment::{Adjust, PointerCast};
1618
use rustc_middle::ty::layout::LayoutOf;
19+
use rustc_middle::ty::{self, RegionKind};
1720
use rustc_session::{declare_tool_lint, impl_lint_pass};
1821
use rustc_span::def_id::LocalDefId;
1922
use rustc_span::{sym, Span};
@@ -141,50 +144,76 @@ impl<'tcx> PassByRefOrValue {
141144
}
142145

143146
let fn_sig = cx.tcx.fn_sig(def_id);
144-
let fn_sig = cx.tcx.erase_late_bound_regions(fn_sig);
145-
146147
let fn_body = cx.enclosing_body.map(|id| cx.tcx.hir().body(id));
147148

148-
for (index, (input, &ty)) in iter::zip(decl.inputs, fn_sig.inputs()).enumerate() {
149+
// Gather all the lifetimes found in the output type which may affect whether
150+
// `TRIVIALLY_COPY_PASS_BY_REF` should be linted.
151+
let mut output_regions = FxHashSet::default();
152+
for_each_top_level_late_bound_region(fn_sig.skip_binder().output(), |region| -> ControlFlow<!> {
153+
output_regions.insert(region);
154+
ControlFlow::Continue(())
155+
});
156+
157+
for (index, (input, ty)) in iter::zip(
158+
decl.inputs,
159+
fn_sig.skip_binder().inputs().iter().map(|&ty| fn_sig.rebind(ty)),
160+
)
161+
.enumerate()
162+
{
149163
// All spans generated from a proc-macro invocation are the same...
150164
match span {
151-
Some(s) if s == input.span => return,
165+
Some(s) if s == input.span => continue,
152166
_ => (),
153167
}
154168

155-
match ty.kind() {
156-
ty::Ref(input_lt, ty, Mutability::Not) => {
157-
// Use lifetimes to determine if we're returning a reference to the
158-
// argument. In that case we can't switch to pass-by-value as the
159-
// argument will not live long enough.
160-
let output_lts = match *fn_sig.output().kind() {
161-
ty::Ref(output_lt, _, _) => vec![output_lt],
162-
ty::Adt(_, substs) => substs.regions().collect(),
163-
_ => vec![],
164-
};
169+
match *ty.skip_binder().kind() {
170+
ty::Ref(lt, ty, Mutability::Not) => {
171+
match lt.kind() {
172+
RegionKind::ReLateBound(index, region)
173+
if index.as_u32() == 0 && output_regions.contains(&region) =>
174+
{
175+
continue;
176+
},
177+
// Early bound regions on functions are either from the containing item, are bounded by another
178+
// lifetime, or are used as a bound for a type or lifetime.
179+
RegionKind::ReEarlyBound(..) => continue,
180+
_ => (),
181+
}
165182

166-
if_chain! {
167-
if !output_lts.contains(input_lt);
168-
if is_copy(cx, *ty);
169-
if let Some(size) = cx.layout_of(*ty).ok().map(|l| l.size.bytes());
170-
if size <= self.ref_min_size;
171-
if let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind;
172-
then {
173-
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
174-
"self".into()
175-
} else {
176-
snippet(cx, decl_ty.span, "_").into()
177-
};
178-
span_lint_and_sugg(
179-
cx,
180-
TRIVIALLY_COPY_PASS_BY_REF,
181-
input.span,
182-
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
183-
"consider passing by value instead",
184-
value_type,
185-
Applicability::Unspecified,
186-
);
183+
let ty = cx.tcx.erase_late_bound_regions(fn_sig.rebind(ty));
184+
if is_copy(cx, ty)
185+
&& let Some(size) = cx.layout_of(ty).ok().map(|l| l.size.bytes())
186+
&& size <= self.ref_min_size
187+
&& let hir::TyKind::Rptr(_, MutTy { ty: decl_ty, .. }) = input.kind
188+
{
189+
if let Some(typeck) = cx.maybe_typeck_results() {
190+
// Don't lint if an unsafe pointer is created.
191+
// TODO: Limit the check only to unsafe pointers to the argument (or part of the argument)
192+
// which escape the current function.
193+
if typeck.node_types().iter().any(|(_, &ty)| ty.is_unsafe_ptr())
194+
|| typeck
195+
.adjustments()
196+
.iter()
197+
.flat_map(|(_, a)| a)
198+
.any(|a| matches!(a.kind, Adjust::Pointer(PointerCast::UnsafeFnPointer)))
199+
{
200+
continue;
201+
}
187202
}
203+
let value_type = if fn_body.and_then(|body| body.params.get(index)).map_or(false, is_self) {
204+
"self".into()
205+
} else {
206+
snippet(cx, decl_ty.span, "_").into()
207+
};
208+
span_lint_and_sugg(
209+
cx,
210+
TRIVIALLY_COPY_PASS_BY_REF,
211+
input.span,
212+
&format!("this argument ({} byte) is passed by reference, but would be more efficient if passed by value (limit: {} byte)", size, self.ref_min_size),
213+
"consider passing by value instead",
214+
value_type,
215+
Applicability::Unspecified,
216+
);
188217
}
189218
},
190219

@@ -196,6 +225,7 @@ impl<'tcx> PassByRefOrValue {
196225
_ => continue,
197226
}
198227
}
228+
let ty = cx.tcx.erase_late_bound_regions(ty);
199229

200230
if_chain! {
201231
if is_copy(cx, ty);

clippy_utils/src/ty.rs

+30-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
#![allow(clippy::module_name_repetitions)]
44

5+
use core::ops::ControlFlow;
56
use rustc_ast::ast::Mutability;
67
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
78
use rustc_hir as hir;
@@ -13,8 +14,8 @@ use rustc_lint::LateContext;
1314
use rustc_middle::mir::interpret::{ConstValue, Scalar};
1415
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
1516
use rustc_middle::ty::{
16-
self, AdtDef, Binder, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Ty, TyCtxt, TypeFoldable, UintTy,
17-
VariantDiscr,
17+
self, AdtDef, Binder, BoundRegion, FnSig, IntTy, ParamEnv, Predicate, PredicateKind, Region, RegionKind, Ty,
18+
TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitor, UintTy, VariantDiscr,
1819
};
1920
use rustc_span::symbol::Ident;
2021
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
@@ -667,3 +668,30 @@ pub fn is_c_void(cx: &LateContext<'_>, ty: Ty<'_>) -> bool {
667668
false
668669
}
669670
}
671+
672+
pub fn for_each_top_level_late_bound_region<B>(
673+
ty: Ty<'_>,
674+
f: impl FnMut(BoundRegion) -> ControlFlow<B>,
675+
) -> ControlFlow<B> {
676+
struct V<F> {
677+
index: u32,
678+
f: F,
679+
}
680+
impl<'tcx, B, F: FnMut(BoundRegion) -> ControlFlow<B>> TypeVisitor<'tcx> for V<F> {
681+
type BreakTy = B;
682+
fn visit_region(&mut self, r: Region<'tcx>) -> ControlFlow<Self::BreakTy> {
683+
if let RegionKind::ReLateBound(idx, bound) = r.kind() && idx.as_u32() == self.index {
684+
(self.f)(bound)
685+
} else {
686+
ControlFlow::Continue(())
687+
}
688+
}
689+
fn visit_binder<T: TypeFoldable<'tcx>>(&mut self, t: &Binder<'tcx, T>) -> ControlFlow<Self::BreakTy> {
690+
self.index += 1;
691+
let res = t.super_visit_with(self);
692+
self.index -= 1;
693+
res
694+
}
695+
}
696+
ty.visit_with(&mut V { index: 0, f })
697+
}

tests/ui/trivially_copy_pass_by_ref.rs

+38
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,44 @@ mod issue5876 {
113113
}
114114
}
115115

116+
fn _ref_to_opt_ref_implicit(x: &u32) -> Option<&u32> {
117+
Some(x)
118+
}
119+
120+
#[allow(clippy::needless_lifetimes)]
121+
fn _ref_to_opt_ref_explicit<'a>(x: &'a u32) -> Option<&'a u32> {
122+
Some(x)
123+
}
124+
125+
fn _with_constraint<'a, 'b: 'a>(x: &'b u32, y: &'a u32) -> &'a u32 {
126+
if true { x } else { y }
127+
}
128+
129+
async fn _async_implicit(x: &u32) -> &u32 {
130+
x
131+
}
132+
133+
#[allow(clippy::needless_lifetimes)]
134+
async fn _async_explicit<'a>(x: &'a u32) -> &'a u32 {
135+
x
136+
}
137+
138+
fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
139+
y
140+
}
141+
142+
fn _return_ptr(x: &u32) -> *const u32 {
143+
x
144+
}
145+
146+
fn _return_field_ptr(x: &(u32, u32)) -> *const u32 {
147+
&x.0
148+
}
149+
150+
fn _return_field_ptr_addr_of(x: &(u32, u32)) -> *const u32 {
151+
core::ptr::addr_of!(x.0)
152+
}
153+
116154
fn main() {
117155
let (mut foo, bar) = (Foo(0), Bar([0; 24]));
118156
let (mut a, b, c, x, y, z) = (0, 0, Bar([0; 24]), 0, Foo(0), 0);

tests/ui/trivially_copy_pass_by_ref.stderr

+7-1
Original file line numberDiff line numberDiff line change
@@ -106,5 +106,11 @@ error: this argument (N byte) is passed by reference, but would be more efficien
106106
LL | fn foo(x: &i32) {
107107
| ^^^^ help: consider passing by value instead: `i32`
108108

109-
error: aborting due to 17 previous errors
109+
error: this argument (N byte) is passed by reference, but would be more efficient if passed by value (limit: N byte)
110+
--> $DIR/trivially_copy_pass_by_ref.rs:138:37
111+
|
112+
LL | fn _unrelated_lifetimes<'a, 'b>(_x: &'a u32, y: &'b u32) -> &'b u32 {
113+
| ^^^^^^^ help: consider passing by value instead: `u32`
114+
115+
error: aborting due to 18 previous errors
110116

0 commit comments

Comments
 (0)