Skip to content

Commit b4384b9

Browse files
committed
large_assignments: Lint on specific large args passed to functions
1 parent 2553a9a commit b4384b9

File tree

5 files changed

+144
-41
lines changed

5 files changed

+144
-41
lines changed

compiler/rustc_middle/src/mir/tcx.rs

+12
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,18 @@ impl<'tcx> Operand<'tcx> {
235235
Operand::Constant(c) => c.const_.ty(),
236236
}
237237
}
238+
239+
pub fn span<D: ?Sized>(&self, local_decls: &D) -> Span
240+
where
241+
D: HasLocalDecls<'tcx>,
242+
{
243+
match self {
244+
&Operand::Copy(ref l) | &Operand::Move(ref l) => {
245+
local_decls.local_decls()[l.local].source_info.span
246+
}
247+
Operand::Constant(c) => c.span,
248+
}
249+
}
238250
}
239251

240252
impl<'tcx> BinOp {

compiler/rustc_monomorphize/src/collector.rs

+75-39
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,8 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
612612
}
613613

614614
fn check_operand_move_size(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
615-
let limit = self.tcx.move_size_limit().0;
616-
if limit == 0 {
615+
let limit = self.tcx.move_size_limit();
616+
if limit.0 == 0 {
617617
return;
618618
}
619619

@@ -625,48 +625,20 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
625625
return;
626626
}
627627

628-
let limit = Size::from_bytes(limit);
629-
let ty = operand.ty(self.body, self.tcx);
630-
let ty = self.monomorphize(ty);
631-
let Ok(layout) = self.tcx.layout_of(ty::ParamEnv::reveal_all().and(ty)) else { return };
632-
if layout.size <= limit {
633-
return;
634-
}
635-
debug!(?layout);
636628
let source_info = self.body.source_info(location);
637629
debug!(?source_info);
638-
for span in &self.move_size_spans {
639-
if span.overlaps(source_info.span) {
640-
return;
641-
}
642-
}
643-
let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
644-
debug!(?lint_root);
645-
let Some(lint_root) = lint_root else {
646-
// This happens when the issue is in a function from a foreign crate that
647-
// we monomorphized in the current crate. We can't get a `HirId` for things
648-
// in other crates.
649-
// FIXME: Find out where to report the lint on. Maybe simply crate-level lint root
650-
// but correct span? This would make the lint at least accept crate-level lint attributes.
651-
return;
630+
631+
if let Some(too_large_size) = self.operand_size_if_too_large(limit, operand) {
632+
self.lint_large_assignment(limit.0, too_large_size, location, source_info.span);
652633
};
653-
self.tcx.emit_spanned_lint(
654-
LARGE_ASSIGNMENTS,
655-
lint_root,
656-
source_info.span,
657-
LargeAssignmentsLint {
658-
span: source_info.span,
659-
size: layout.size.bytes(),
660-
limit: limit.bytes(),
661-
},
662-
);
663-
self.move_size_spans.push(source_info.span);
664634
}
665635

666636
fn check_fn_args_move_size(
667637
&mut self,
668638
callee_ty: Ty<'tcx>,
669639
args: &[mir::Operand<'tcx>],
640+
arg_spans: &[Span],
641+
fn_span: Span,
670642
location: Location,
671643
) {
672644
let limit = self.tcx.move_size_limit();
@@ -678,6 +650,14 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
678650
return;
679651
}
680652

653+
// Not exactly sure yet how these can get out of sync? Maybe has to do
654+
// with bootstrapping and staged builds? If things are in sync, we
655+
// should be able to expect args and arg_spans to always have the same
656+
// length. Could also be a bug in lowering...
657+
if args.len() != arg_spans.len() {
658+
return;
659+
}
660+
681661
// Allow large moves into container types that themselves are cheap to move
682662
let ty::FnDef(def_id, _) = *callee_ty.kind() else {
683663
return;
@@ -690,10 +670,66 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
690670
return;
691671
}
692672

693-
for arg in args {
694-
self.check_operand_move_size(arg, location);
673+
debug!(?def_id, ?fn_span);
674+
assert_eq!(args.len(), arg_spans.len());
675+
676+
for (idx, arg) in args.iter().enumerate() {
677+
if let Some(too_large_size) = self.operand_size_if_too_large(limit, arg) {
678+
self.lint_large_assignment(limit.0, too_large_size, location, arg_spans[idx]);
679+
};
695680
}
696681
}
682+
683+
fn operand_size_if_too_large(
684+
&mut self,
685+
limit: Limit,
686+
operand: &mir::Operand<'tcx>,
687+
) -> Option<Size> {
688+
let ty = operand.ty(self.body, self.tcx);
689+
let ty = self.monomorphize(ty);
690+
let Ok(layout) = self.tcx.layout_of(ty::ParamEnv::reveal_all().and(ty)) else {
691+
return None;
692+
};
693+
if layout.size.bytes_usize() > limit.0 {
694+
debug!(?layout);
695+
Some(layout.size)
696+
} else {
697+
None
698+
}
699+
}
700+
701+
fn lint_large_assignment(
702+
&mut self,
703+
limit: usize,
704+
too_large_size: Size,
705+
location: Location,
706+
span: Span,
707+
) {
708+
let source_info = self.body.source_info(location);
709+
debug!(?source_info);
710+
for reported_span in &self.move_size_spans {
711+
if reported_span.overlaps(span) {
712+
return;
713+
}
714+
}
715+
let lint_root = source_info.scope.lint_root(&self.body.source_scopes);
716+
debug!(?lint_root);
717+
let Some(lint_root) = lint_root else {
718+
// This happens when the issue is in a function from a foreign crate that
719+
// we monomorphized in the current crate. We can't get a `HirId` for things
720+
// in other crates.
721+
// FIXME: Find out where to report the lint on. Maybe simply crate-level lint root
722+
// but correct span? This would make the lint at least accept crate-level lint attributes.
723+
return;
724+
};
725+
self.tcx.emit_spanned_lint(
726+
LARGE_ASSIGNMENTS,
727+
lint_root,
728+
span,
729+
LargeAssignmentsLint { span, size: too_large_size.bytes(), limit: limit as u64 },
730+
);
731+
self.move_size_spans.push(span);
732+
}
697733
}
698734

699735
impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
@@ -812,11 +848,11 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
812848

813849
match terminator.kind {
814850
mir::TerminatorKind::Call {
815-
ref func, ref args, ..
851+
ref func, ref args, ref arg_spans, ref fn_span, ..
816852
} => {
817853
let callee_ty = func.ty(self.body, tcx);
818854
let callee_ty = self.monomorphize(callee_ty);
819-
self.check_fn_args_move_size(callee_ty, args, location);
855+
self.check_fn_args_move_size(callee_ty, args, arg_spans, *fn_span, location);
820856
visit_fn_use(self.tcx, callee_ty, true, source, &mut self.output)
821857
}
822858
mir::TerminatorKind::Drop { ref place, .. } => {

tests/ui/lint/large_assignments/box_rc_arc_allowed.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: moving 9999 bytes
2-
--> $DIR/box_rc_arc_allowed.rs:16:13
2+
--> $DIR/box_rc_arc_allowed.rs:16:25
33
|
44
LL | let _ = NotBox::new([0; 9999]);
5-
| ^^^^^^^^^^^^^^^^^^^^^^ value moved from here
5+
| ^^^^^^^^^ value moved from here
66
|
77
= note: The current maximum size is 1000, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]`
88
note: the lint level is defined here
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// build-fail
2+
3+
#![feature(large_assignments)]
4+
#![move_size_limit = "1000"]
5+
#![deny(large_assignments)]
6+
#![allow(unused)]
7+
8+
// We want copy semantics, because moving data into functions generally do not
9+
// translate to actual `memcpy`s.
10+
#[derive(Copy, Clone)]
11+
struct Data([u8; 9999]);
12+
13+
fn main() {
14+
one_arg(Data([0; 9999])); //~ ERROR large_assignments
15+
16+
// each individual large arg shall have its own span
17+
many_args(Data([0; 9999]), true, Data([0; 9999]));
18+
//~^ ERROR large_assignments
19+
//~| ERROR large_assignments
20+
}
21+
22+
fn one_arg(a: Data) {}
23+
24+
fn many_args(a: Data, b: bool, c: Data) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
error: moving 9999 bytes
2+
--> $DIR/copy_into_fn.rs:14:13
3+
|
4+
LL | one_arg(Data([0; 9999]));
5+
| ^^^^^^^^^^^^^^^ value moved from here
6+
|
7+
= note: The current maximum size is 1000, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]`
8+
note: the lint level is defined here
9+
--> $DIR/copy_into_fn.rs:5:9
10+
|
11+
LL | #![deny(large_assignments)]
12+
| ^^^^^^^^^^^^^^^^^
13+
14+
error: moving 9999 bytes
15+
--> $DIR/copy_into_fn.rs:17:15
16+
|
17+
LL | many_args(Data([0; 9999]), true, Data([0; 9999]));
18+
| ^^^^^^^^^^^^^^^ value moved from here
19+
|
20+
= note: The current maximum size is 1000, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]`
21+
22+
error: moving 9999 bytes
23+
--> $DIR/copy_into_fn.rs:17:38
24+
|
25+
LL | many_args(Data([0; 9999]), true, Data([0; 9999]));
26+
| ^^^^^^^^^^^^^^^ value moved from here
27+
|
28+
= note: The current maximum size is 1000, but it can be customized with the move_size_limit attribute: `#![move_size_limit = "..."]`
29+
30+
error: aborting due to 3 previous errors
31+

0 commit comments

Comments
 (0)