/
trivially_copy_pass_by_ref.rs
179 lines (163 loc) · 6.27 KB
/
trivially_copy_pass_by_ref.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use std::cmp;
use crate::utils::{is_copy, is_self_ty, snippet, span_lint_and_sugg};
use if_chain::if_chain;
use matches::matches;
use rustc::hir;
use rustc::hir::intravisit::FnKind;
use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc::session::config::Config as SessionConfig;
use rustc::ty;
use rustc::{declare_tool_lint, impl_lint_pass};
use rustc_errors::Applicability;
use rustc_target::abi::LayoutOf;
use rustc_target::spec::abi::Abi;
use syntax_pos::Span;
declare_clippy_lint! {
/// **What it does:** Checks for functions taking arguments by reference, where
/// the argument type is `Copy` and small enough to be more efficient to always
/// pass by value.
///
/// **Why is this bad?** In many calling conventions instances of structs will
/// be passed through registers if they fit into two or less general purpose
/// registers.
///
/// **Known problems:** This lint is target register size dependent, it is
/// limited to 32-bit to try and reduce portability problems between 32 and
/// 64-bit, but if you are compiling for 8 or 16-bit targets then the limit
/// will be different.
///
/// The configuration option `trivial_copy_size_limit` can be set to override
/// this limit for a project.
///
/// This lint attempts to allow passing arguments by reference if a reference
/// to that argument is returned. This is implemented by comparing the lifetime
/// of the argument and return value for equality. However, this can cause
/// false positives in cases involving multiple lifetimes that are bounded by
/// each other.
///
/// **Example:**
///
/// ```rust
/// // Bad
/// fn foo(v: &u32) {}
/// ```
///
/// ```rust
/// // Better
/// fn foo(v: u32) {}
/// ```
pub TRIVIALLY_COPY_PASS_BY_REF,
perf,
"functions taking small copyable arguments by reference"
}
#[derive(Copy, Clone)]
pub struct TriviallyCopyPassByRef {
limit: u64,
}
impl<'a, 'tcx> TriviallyCopyPassByRef {
pub fn new(limit: Option<u64>, target: &SessionConfig) -> Self {
let limit = limit.unwrap_or_else(|| {
let bit_width = target.usize_ty.bit_width().expect("usize should have a width") as u64;
// Cap the calculated bit width at 32-bits to reduce
// portability problems between 32 and 64-bit targets
let bit_width = cmp::min(bit_width, 32);
#[allow(clippy::integer_division)]
let byte_width = bit_width / 8;
// Use a limit of 2 times the register byte width
byte_width * 2
});
Self { limit }
}
fn check_poly_fn(&mut self, cx: &LateContext<'_, 'tcx>, hir_id: HirId, decl: &FnDecl, span: Option<Span>) {
let fn_def_id = cx.tcx.hir().local_def_id(hir_id);
let fn_sig = cx.tcx.fn_sig(fn_def_id);
let fn_sig = cx.tcx.erase_late_bound_regions(&fn_sig);
// 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![],
};
for (input, &ty) in decl.inputs.iter().zip(fn_sig.inputs()) {
// All spans generated from a proc-macro invocation are the same...
match span {
Some(s) if s == input.span => return,
_ => (),
}
if_chain! {
if let ty::Ref(input_lt, ty, Mutability::Immutable) = ty.kind;
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.limit;
if let hir::TyKind::Rptr(_, MutTy { ty: ref decl_ty, .. }) = input.kind;
then {
let value_type = if is_self_ty(decl_ty) {
"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.limit),
"consider passing by value instead",
value_type,
Applicability::Unspecified,
);
}
}
}
}
}
impl_lint_pass!(TriviallyCopyPassByRef => [TRIVIALLY_COPY_PASS_BY_REF]);
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for TriviallyCopyPassByRef {
fn check_trait_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::TraitItem) {
if item.span.from_expansion() {
return;
}
if let hir::TraitItemKind::Method(method_sig, _) = &item.kind {
self.check_poly_fn(cx, item.hir_id, &*method_sig.decl, None);
}
}
fn check_fn(
&mut self,
cx: &LateContext<'a, 'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl,
_body: &'tcx Body,
span: Span,
hir_id: HirId,
) {
if span.from_expansion() {
return;
}
match kind {
FnKind::ItemFn(.., header, _, attrs) => {
if header.abi != Abi::Rust {
return;
}
for a in attrs {
if a.meta_item_list().is_some() && a.check_name(sym!(proc_macro_derive)) {
return;
}
}
},
FnKind::Method(..) => (),
_ => return,
}
// Exclude non-inherent impls
if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
if matches!(item.kind, ItemKind::Impl(_, _, _, _, Some(_), _, _) |
ItemKind::Trait(..))
{
return;
}
}
self.check_poly_fn(cx, hir_id, decl, Some(span));
}
}