use rustc_session::lint::builtin::LARGE_ASSIGNMENTS; use super::*; use crate::errors::LargeAssignmentsLint; pub(super) struct MoveCheckState { /// Spans for move size lints already emitted. Helps avoid duplicate lints. move_size_spans: Vec, /// Set of functions for which it is OK to move large data into. skip_move_check_fns: Option>, } impl MoveCheckState { pub(super) fn new() -> Self { MoveCheckState { move_size_spans: vec![], skip_move_check_fns: None } } } impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> { pub(super) fn check_operand_move_size( &mut self, operand: &mir::Operand<'tcx>, location: Location, ) { let limit = self.tcx.move_size_limit(); if limit.0 == 0 { return; } // This function is called by visit_operand() which visits _all_ // operands, including TerminatorKind::Call operands. But if // check_fn_args_move_size() has been called, the operands have already // been visited. Do not visit them again. if self.visiting_call_terminator { return; } let source_info = self.body.source_info(location); debug!(?source_info); if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) { self.lint_large_assignment(limit.0, too_large_size, location, source_info.span); }; } pub(super) fn check_fn_args_move_size( &mut self, callee_ty: Ty<'tcx>, args: &[Spanned>], fn_span: Span, location: Location, ) { let limit = self.tcx.move_size_limit(); if limit.0 == 0 { return; } if args.is_empty() { return; } // Allow large moves into container types that themselves are cheap to move let ty::FnDef(def_id, _) = *callee_ty.kind() else { return; }; if self .move_check .skip_move_check_fns .get_or_insert_with(|| build_skip_move_check_fns(self.tcx)) .contains(&def_id) { return; } debug!(?def_id, ?fn_span); for arg in args { // Moving args into functions is typically implemented with pointer // passing at the llvm-ir level and not by memcpy's. So always allow // moving args into functions. let operand: &mir::Operand<'tcx> = &arg.node; if let mir::Operand::Move(_) = operand { continue; } if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) { self.lint_large_assignment(limit.0, too_large_size, location, arg.span); }; } } fn operand_size_if_too_large( &mut self, limit: Limit, operand: &mir::Operand<'tcx>, ) -> Option { let ty = operand.ty(self.body, self.tcx); let ty = self.monomorphize(ty); let Ok(layout) = self.tcx.layout_of(ty::ParamEnv::reveal_all().and(ty)) else { return None; }; if layout.size.bytes_usize() > limit.0 { debug!(?layout); Some(layout.size) } else { None } } fn lint_large_assignment( &mut self, limit: usize, too_large_size: Size, location: Location, span: Span, ) { let source_info = self.body.source_info(location); debug!(?source_info); for reported_span in &self.move_check.move_size_spans { if reported_span.overlaps(span) { return; } } let lint_root = source_info.scope.lint_root(&self.body.source_scopes); debug!(?lint_root); let Some(lint_root) = lint_root else { // This happens when the issue is in a function from a foreign crate that // we monomorphized in the current crate. We can't get a `HirId` for things // in other crates. // FIXME: Find out where to report the lint on. Maybe simply crate-level lint root // but correct span? This would make the lint at least accept crate-level lint attributes. return; }; self.tcx.emit_node_span_lint( LARGE_ASSIGNMENTS, lint_root, span, LargeAssignmentsLint { span, size: too_large_size.bytes(), limit: limit as u64 }, ); self.move_check.move_size_spans.push(span); } } fn build_skip_move_check_fns(tcx: TyCtxt<'_>) -> Vec { let fns = [ (tcx.lang_items().owned_box(), "new"), (tcx.get_diagnostic_item(sym::Rc), "new"), (tcx.get_diagnostic_item(sym::Arc), "new"), ]; fns.into_iter() .filter_map(|(def_id, fn_name)| { def_id.and_then(|def_id| assoc_fn_of_type(tcx, def_id, Ident::from_str(fn_name))) }) .collect::>() }