Skip to main content

rustc_monomorphize/mono_checks/
move_check.rs

1use rustc_abi::Size;
2use rustc_hir::def::DefKind;
3use rustc_hir::def_id::DefId;
4use rustc_lint_defs::builtin::LARGE_ASSIGNMENTS;
5use rustc_middle::mir::visit::Visitor as MirVisitor;
6use rustc_middle::mir::{self, Location, traversal};
7use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeFoldable};
8use rustc_span::{Span, Spanned, sym};
9use rustc_structures::Limit;
10use tracing::{debug, trace};
11
12use crate::diagnostics::LargeAssignmentsLint;
13
14struct MoveCheckVisitor<'tcx> {
15    tcx: TyCtxt<'tcx>,
16    instance: Instance<'tcx>,
17    body: &'tcx mir::Body<'tcx>,
18    /// Spans for move size lints already emitted. Helps avoid duplicate lints.
19    move_size_spans: Vec<Span>,
20}
21
22pub(crate) fn check_moves<'tcx>(
23    tcx: TyCtxt<'tcx>,
24    instance: Instance<'tcx>,
25    body: &'tcx mir::Body<'tcx>,
26) {
27    if tcx.move_size_limit().0 == 0 {
28        return;
29    }
30
31    let mut visitor = MoveCheckVisitor { tcx, instance, body, move_size_spans: ::alloc::vec::Vec::new()vec![] };
32    for (bb, data) in traversal::mono_reachable(body, tcx, instance) {
33        visitor.visit_basic_block_data(bb, data)
34    }
35}
36
37impl<'tcx> MirVisitor<'tcx> for MoveCheckVisitor<'tcx> {
38    fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
39        match terminator.kind {
40            mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. }
41            | mir::TerminatorKind::TailCall { ref func, ref args, ref fn_span } => {
42                let callee_ty = func.ty(self.body, self.tcx);
43                let callee_ty = self.monomorphize(callee_ty);
44                self.check_fn_args_move_size(callee_ty, args, *fn_span, location);
45            }
46            _ => {}
47        }
48
49        // We deliberately do *not* visit the nested operands here, to avoid
50        // hitting `visit_operand` for function arguments.
51    }
52
53    fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
54        self.check_operand_move_size(operand, location);
55    }
56}
57
58impl<'tcx> MoveCheckVisitor<'tcx> {
59    fn monomorphize<T>(&self, value: T) -> T
60    where
61        T: TypeFoldable<TyCtxt<'tcx>>,
62    {
63        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs:63",
                        "rustc_monomorphize::mono_checks::move_check",
                        ::tracing::Level::TRACE,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs"),
                        ::tracing_core::__macro_support::Option::Some(63u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_monomorphize::mono_checks::move_check"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::TRACE <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::TRACE <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("monomorphize: self.instance={0:?}",
                                                    self.instance) as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};trace!("monomorphize: self.instance={:?}", self.instance);
64        self.instance.instantiate_mir_and_normalize_erasing_regions(
65            self.tcx,
66            ty::TypingEnv::fully_monomorphized(),
67            ty::EarlyBinder::bind(self.tcx, value),
68        )
69    }
70
71    fn check_operand_move_size(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
72        let limit = self.tcx.move_size_limit();
73        if limit.0 == 0 {
74            return;
75        }
76
77        let source_info = self.body.source_info(location);
78        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs:78",
                        "rustc_monomorphize::mono_checks::move_check",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs"),
                        ::tracing_core::__macro_support::Option::Some(78u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_monomorphize::mono_checks::move_check"),
                        ::tracing_core::field::FieldSet::new(&[{
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("source_info")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("source_info");
                                            NAME.as_str()
                                        }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&source_info)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(?source_info);
79
80        if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
81            self.lint_large_assignment(limit.0, too_large_size, location, source_info.span);
82        };
83    }
84
85    pub(super) fn check_fn_args_move_size(
86        &mut self,
87        callee_ty: Ty<'tcx>,
88        args: &[Spanned<mir::Operand<'tcx>>],
89        fn_span: Span,
90        location: Location,
91    ) {
92        let limit = self.tcx.move_size_limit();
93        if limit.0 == 0 {
94            return;
95        }
96
97        if args.is_empty() {
98            return;
99        }
100
101        // Allow large moves into container types that themselves are cheap to move
102        let ty::FnDef(def_id, _) = *callee_ty.kind() else {
103            return;
104        };
105        if should_skip_fn(self.tcx, def_id) {
106            return;
107        }
108
109        {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs:109",
                        "rustc_monomorphize::mono_checks::move_check",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs"),
                        ::tracing_core::__macro_support::Option::Some(109u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_monomorphize::mono_checks::move_check"),
                        ::tracing_core::field::FieldSet::new(&[{
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("def_id")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("def_id");
                                            NAME.as_str()
                                        },
                                        {
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("fn_span")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("fn_span");
                                            NAME.as_str()
                                        }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&def_id)
                                            as &dyn ::tracing::field::Value)),
                                (::tracing::__macro_support::Option::Some(&::tracing::field::debug(&fn_span)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(?def_id, ?fn_span);
110
111        for arg in args {
112            // Moving args into functions is typically implemented with pointer
113            // passing at the llvm-ir level and not by memcpy's. So always allow
114            // moving args into functions.
115            let operand: &mir::Operand<'tcx> = &arg.node;
116            if let mir::Operand::Move(_) = operand {
117                continue;
118            }
119
120            if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
121                self.lint_large_assignment(limit.0, too_large_size, location, arg.span);
122            };
123        }
124    }
125
126    fn operand_size_if_too_large(
127        &mut self,
128        limit: Limit,
129        operand: &mir::Operand<'tcx>,
130    ) -> Option<Size> {
131        let ty = operand.ty(self.body, self.tcx);
132        let ty = self.monomorphize(ty);
133        let Ok(layout) =
134            self.tcx.layout_of(ty::TypingEnv::fully_monomorphized().as_query_input(ty))
135        else {
136            return None;
137        };
138        if layout.size.bytes_usize() > limit.0 {
139            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event /rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs:139",
                        "rustc_monomorphize::mono_checks::move_check",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("/rustc-dev/5d8fec0669a64607db9ccbf663e7c23125bf4748/compiler/rustc_monomorphize/src/mono_checks/move_check.rs"),
                        ::tracing_core::__macro_support::Option::Some(139u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_monomorphize::mono_checks::move_check"),
                        ::tracing_core::field::FieldSet::new(&[{
                                            const NAME:
                                                ::tracing::__macro_support::FieldName<{
                                                    ::tracing::__macro_support::FieldName::len("layout")
                                                }> =
                                                ::tracing::__macro_support::FieldName::new("layout");
                                            NAME.as_str()
                                        }], ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                __CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::debug(&layout)
                                            as &dyn ::tracing::field::Value))])
            });
    } else { ; }
};debug!(?layout);
140            Some(layout.size)
141        } else {
142            None
143        }
144    }
145
146    fn lint_large_assignment(
147        &mut self,
148        limit: usize,
149        too_large_size: Size,
150        location: Location,
151        span: Span,
152    ) {
153        let source_info = self.body.source_info(location);
154
155        let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
156        let Some(lint_root) = lint_root else {
157            // This happens when the issue is in a function from a foreign crate that
158            // we monomorphized in the current crate. We can't get a `HirId` for things
159            // in other crates.
160            // FIXME: Find out where to report the lint on. Maybe simply crate-level lint root
161            // but correct span? This would make the lint at least accept crate-level lint attributes.
162            return;
163        };
164
165        // If the source scope is inlined by the MIR inliner, report the lint on the call site.
166        let reported_span = self
167            .body
168            .source_scopes
169            .get(source_info.scope)
170            .and_then(|source_scope_data| source_scope_data.inlined)
171            .map(|(_, call_site)| call_site)
172            .unwrap_or(span);
173
174        for previously_reported_span in &self.move_size_spans {
175            if previously_reported_span.overlaps(reported_span) {
176                return;
177            }
178        }
179
180        self.tcx.emit_node_span_lint(
181            LARGE_ASSIGNMENTS,
182            lint_root,
183            reported_span,
184            LargeAssignmentsLint {
185                span: reported_span,
186                size: too_large_size.bytes(),
187                limit: limit as u64,
188            },
189        );
190
191        self.move_size_spans.push(reported_span);
192    }
193}
194
195/// Return `true` if `def_id` is `Box::new`, `Rc::new` or `Arc::new`.
196fn should_skip_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
197    if let DefKind::AssocFn = tcx.def_kind(def_id)
198        && tcx.item_name(def_id) == sym::new
199        && let parent = tcx.parent(def_id)
200        && let DefKind::Impl { of_trait: false } = tcx.def_kind(parent)
201        && let ty::Adt(adt_def, ..) =
202            tcx.type_of(parent).instantiate_identity().skip_normalization().kind()
203    {
204        return Some(adt_def.did()) == tcx.lang_items().owned_box()
205            || Some(adt_def.did()) == tcx.get_diagnostic_item(sym::Rc)
206            || Some(adt_def.did()) == tcx.get_diagnostic_item(sym::Arc);
207    }
208    false
209}