rustc_monomorphize/mono_checks/
move_check.rs

1use 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, AssocKind, 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    /// Spans for move size lints already emitted. Helps avoid duplicate lints.
20    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        // We deliberately do *not* visit the nested operands here, to avoid
47        // hitting `visit_operand` for function arguments.
48    }
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        // Allow large moves into container types that themselves are cheap to move
99        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            // Moving args into functions is typically implemented with pointer
110            // passing at the llvm-ir level and not by memcpy's. So always allow
111            // moving args into functions.
112            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        for reported_span in &self.move_size_spans {
152            if reported_span.overlaps(span) {
153                return;
154            }
155        }
156        let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
157        let Some(lint_root) = lint_root else {
158            // This happens when the issue is in a function from a foreign crate that
159            // we monomorphized in the current crate. We can't get a `HirId` for things
160            // in other crates.
161            // FIXME: Find out where to report the lint on. Maybe simply crate-level lint root
162            // but correct span? This would make the lint at least accept crate-level lint attributes.
163            return;
164        };
165        self.tcx.emit_node_span_lint(
166            LARGE_ASSIGNMENTS,
167            lint_root,
168            span,
169            LargeAssignmentsLint { span, size: too_large_size.bytes(), limit: limit as u64 },
170        );
171        self.move_size_spans.push(span);
172    }
173}
174
175fn assoc_fn_of_type<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, fn_ident: Ident) -> Option<DefId> {
176    for impl_def_id in tcx.inherent_impls(def_id) {
177        if let Some(new) = tcx.associated_items(impl_def_id).find_by_name_and_kind(
178            tcx,
179            fn_ident,
180            AssocKind::Fn,
181            def_id,
182        ) {
183            return Some(new.def_id);
184        }
185    }
186    None
187}
188
189pub(crate) fn skip_move_check_fns(tcx: TyCtxt<'_>, _: ()) -> FxIndexSet<DefId> {
190    let fns = [
191        (tcx.lang_items().owned_box(), "new"),
192        (tcx.get_diagnostic_item(sym::Rc), "new"),
193        (tcx.get_diagnostic_item(sym::Arc), "new"),
194    ];
195    fns.into_iter()
196        .filter_map(|(def_id, fn_name)| {
197            def_id.and_then(|def_id| assoc_fn_of_type(tcx, def_id, Ident::from_str(fn_name)))
198        })
199        .collect()
200}