rustc_monomorphize/mono_checks/
move_check.rs1use rustc_abi::Size;
2use rustc_data_structures::fx::FxIndexSet;
3use rustc_hir::def_id::DefId;
4use rustc_middle::mir::visit::Visitor as MirVisitor;
5use rustc_middle::mir::{self, Location, traversal};
6use rustc_middle::ty::{self, AssocTag, Instance, Ty, TyCtxt, TypeFoldable};
7use rustc_session::Limit;
8use rustc_session::lint::builtin::LARGE_ASSIGNMENTS;
9use rustc_span::source_map::Spanned;
10use rustc_span::{Ident, Span, sym};
11use tracing::{debug, trace};
12
13use crate::errors::LargeAssignmentsLint;
14
15struct MoveCheckVisitor<'tcx> {
16 tcx: TyCtxt<'tcx>,
17 instance: Instance<'tcx>,
18 body: &'tcx mir::Body<'tcx>,
19 move_size_spans: Vec<Span>,
21}
22
23pub(crate) fn check_moves<'tcx>(
24 tcx: TyCtxt<'tcx>,
25 instance: Instance<'tcx>,
26 body: &'tcx mir::Body<'tcx>,
27) {
28 let mut visitor = MoveCheckVisitor { tcx, instance, body, move_size_spans: vec![] };
29 for (bb, data) in traversal::mono_reachable(body, tcx, instance) {
30 visitor.visit_basic_block_data(bb, data)
31 }
32}
33
34impl<'tcx> MirVisitor<'tcx> for MoveCheckVisitor<'tcx> {
35 fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
36 match terminator.kind {
37 mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. }
38 | mir::TerminatorKind::TailCall { ref func, ref args, ref fn_span } => {
39 let callee_ty = func.ty(self.body, self.tcx);
40 let callee_ty = self.monomorphize(callee_ty);
41 self.check_fn_args_move_size(callee_ty, args, *fn_span, location);
42 }
43 _ => {}
44 }
45
46 }
49
50 fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
51 self.check_operand_move_size(operand, location);
52 }
53}
54
55impl<'tcx> MoveCheckVisitor<'tcx> {
56 fn monomorphize<T>(&self, value: T) -> T
57 where
58 T: TypeFoldable<TyCtxt<'tcx>>,
59 {
60 trace!("monomorphize: self.instance={:?}", self.instance);
61 self.instance.instantiate_mir_and_normalize_erasing_regions(
62 self.tcx,
63 ty::TypingEnv::fully_monomorphized(),
64 ty::EarlyBinder::bind(value),
65 )
66 }
67
68 fn check_operand_move_size(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
69 let limit = self.tcx.move_size_limit();
70 if limit.0 == 0 {
71 return;
72 }
73
74 let source_info = self.body.source_info(location);
75 debug!(?source_info);
76
77 if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
78 self.lint_large_assignment(limit.0, too_large_size, location, source_info.span);
79 };
80 }
81
82 pub(super) fn check_fn_args_move_size(
83 &mut self,
84 callee_ty: Ty<'tcx>,
85 args: &[Spanned<mir::Operand<'tcx>>],
86 fn_span: Span,
87 location: Location,
88 ) {
89 let limit = self.tcx.move_size_limit();
90 if limit.0 == 0 {
91 return;
92 }
93
94 if args.is_empty() {
95 return;
96 }
97
98 let ty::FnDef(def_id, _) = *callee_ty.kind() else {
100 return;
101 };
102 if self.tcx.skip_move_check_fns(()).contains(&def_id) {
103 return;
104 }
105
106 debug!(?def_id, ?fn_span);
107
108 for arg in args {
109 let operand: &mir::Operand<'tcx> = &arg.node;
113 if let mir::Operand::Move(_) = operand {
114 continue;
115 }
116
117 if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
118 self.lint_large_assignment(limit.0, too_large_size, location, arg.span);
119 };
120 }
121 }
122
123 fn operand_size_if_too_large(
124 &mut self,
125 limit: Limit,
126 operand: &mir::Operand<'tcx>,
127 ) -> Option<Size> {
128 let ty = operand.ty(self.body, self.tcx);
129 let ty = self.monomorphize(ty);
130 let Ok(layout) =
131 self.tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
132 else {
133 return None;
134 };
135 if layout.size.bytes_usize() > limit.0 {
136 debug!(?layout);
137 Some(layout.size)
138 } else {
139 None
140 }
141 }
142
143 fn lint_large_assignment(
144 &mut self,
145 limit: usize,
146 too_large_size: Size,
147 location: Location,
148 span: Span,
149 ) {
150 let source_info = self.body.source_info(location);
151
152 let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
153 let Some(lint_root) = lint_root else {
154 return;
160 };
161
162 let reported_span = self
164 .body
165 .source_scopes
166 .get(source_info.scope)
167 .and_then(|source_scope_data| source_scope_data.inlined)
168 .map(|(_, call_site)| call_site)
169 .unwrap_or(span);
170
171 for previously_reported_span in &self.move_size_spans {
172 if previously_reported_span.overlaps(reported_span) {
173 return;
174 }
175 }
176
177 self.tcx.emit_node_span_lint(
178 LARGE_ASSIGNMENTS,
179 lint_root,
180 reported_span,
181 LargeAssignmentsLint {
182 span: reported_span,
183 size: too_large_size.bytes(),
184 limit: limit as u64,
185 },
186 );
187
188 self.move_size_spans.push(reported_span);
189 }
190}
191
192fn assoc_fn_of_type<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, fn_ident: Ident) -> Option<DefId> {
193 for impl_def_id in tcx.inherent_impls(def_id) {
194 if let Some(new) = tcx.associated_items(impl_def_id).find_by_ident_and_kind(
195 tcx,
196 fn_ident,
197 AssocTag::Fn,
198 def_id,
199 ) {
200 return Some(new.def_id);
201 }
202 }
203 None
204}
205
206pub(crate) fn skip_move_check_fns(tcx: TyCtxt<'_>, _: ()) -> FxIndexSet<DefId> {
207 let fns = [
208 (tcx.lang_items().owned_box(), "new"),
209 (tcx.get_diagnostic_item(sym::Rc), "new"),
210 (tcx.get_diagnostic_item(sym::Arc), "new"),
211 ];
212 fns.into_iter()
213 .filter_map(|(def_id, fn_name)| {
214 def_id.and_then(|def_id| assoc_fn_of_type(tcx, def_id, Ident::from_str(fn_name)))
215 })
216 .collect()
217}