Skip to content

Commit 97ba291

Browse files
committed
Auto merge of #12582 - kpreid:stacksize, r=Manishearth
`large_stack_frames`: print total size and largest component. Instead of just saying “this function's stack frame is big”, report: * the (presumed) size of the frame * the size and type of the largest local contributing to that size * the configurable limit that was exceeded (once) Known issues: * The lint may report an over-estimate because codegen may be able to overlap some of these locals. However, that already affected whether the lint fired at all; I believe this change is still an improvement because it gives the user much more actionable information about _why_ the lint fired. * Please tell me a better way to determine whether a local has a variable name. changelog: [`large_stack_frames`]: print total size and largest component.
2 parents 124e68b + 0164645 commit 97ba291

File tree

5 files changed

+136
-70
lines changed

5 files changed

+136
-70
lines changed

clippy_lints/src/large_stack_frames.rs

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
use std::ops::AddAssign;
1+
use std::{fmt, ops};
22

3-
use clippy_utils::diagnostics::span_lint_and_note;
3+
use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::fn_has_unsatisfiable_preds;
5+
use clippy_utils::source::snippet_opt;
56
use rustc_hir::def_id::LocalDefId;
67
use rustc_hir::intravisit::FnKind;
78
use rustc_hir::{Body, FnDecl};
9+
use rustc_lexer::is_ident;
810
use rustc_lint::{LateContext, LateLintPass};
911
use rustc_session::impl_lint_pass;
1012
use rustc_span::Span;
@@ -108,13 +110,25 @@ impl Space {
108110
}
109111
}
110112

111-
impl AddAssign<u64> for Space {
112-
fn add_assign(&mut self, rhs: u64) {
113-
if let Self::Used(lhs) = self {
114-
match lhs.checked_add(rhs) {
115-
Some(sum) => *self = Self::Used(sum),
116-
None => *self = Self::Overflow,
117-
}
113+
impl fmt::Display for Space {
114+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115+
match self {
116+
Space::Used(1) => write!(f, "1 byte"),
117+
Space::Used(n) => write!(f, "{n} bytes"),
118+
Space::Overflow => write!(f, "over 2⁶⁴-1 bytes"),
119+
}
120+
}
121+
}
122+
123+
impl ops::Add<u64> for Space {
124+
type Output = Self;
125+
fn add(self, rhs: u64) -> Self {
126+
match self {
127+
Self::Used(lhs) => match lhs.checked_add(rhs) {
128+
Some(sum) => Self::Used(sum),
129+
None => Self::Overflow,
130+
},
131+
Self::Overflow => self,
118132
}
119133
}
120134
}
@@ -123,10 +137,10 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
123137
fn check_fn(
124138
&mut self,
125139
cx: &LateContext<'tcx>,
126-
_: FnKind<'tcx>,
140+
fn_kind: FnKind<'tcx>,
127141
_: &'tcx FnDecl<'tcx>,
128142
_: &'tcx Body<'tcx>,
129-
span: Span,
143+
entire_fn_span: Span,
130144
local_def_id: LocalDefId,
131145
) {
132146
let def_id = local_def_id.to_def_id();
@@ -138,22 +152,68 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
138152
let mir = cx.tcx.optimized_mir(def_id);
139153
let param_env = cx.tcx.param_env(def_id);
140154

141-
let mut frame_size = Space::Used(0);
155+
let sizes_of_locals = || {
156+
mir.local_decls.iter().filter_map(|local| {
157+
let layout = cx.tcx.layout_of(param_env.and(local.ty)).ok()?;
158+
Some((local, layout.size.bytes()))
159+
})
160+
};
142161

143-
for local in &mir.local_decls {
144-
if let Ok(layout) = cx.tcx.layout_of(param_env.and(local.ty)) {
145-
frame_size += layout.size.bytes();
146-
}
147-
}
162+
let frame_size = sizes_of_locals().fold(Space::Used(0), |sum, (_, size)| sum + size);
163+
164+
let limit = self.maximum_allowed_size;
165+
if frame_size.exceeds_limit(limit) {
166+
// Point at just the function name if possible, because lints that span
167+
// the entire body and don't have to are less legible.
168+
let fn_span = match fn_kind {
169+
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
170+
FnKind::Closure => entire_fn_span,
171+
};
148172

149-
if frame_size.exceeds_limit(self.maximum_allowed_size) {
150-
span_lint_and_note(
173+
span_lint_and_then(
151174
cx,
152175
LARGE_STACK_FRAMES,
153-
span,
154-
"this function allocates a large amount of stack space",
155-
None,
156-
"allocating large amounts of stack space can overflow the stack",
176+
fn_span,
177+
&format!("this function may allocate {frame_size} on the stack"),
178+
|diag| {
179+
// Point out the largest individual contribution to this size, because
180+
// it is the most likely to be unintentionally large.
181+
if let Some((local, size)) = sizes_of_locals().max_by_key(|&(_, size)| size) {
182+
let local_span: Span = local.source_info.span;
183+
let size = Space::Used(size); // pluralizes for us
184+
let ty = local.ty;
185+
186+
// TODO: Is there a cleaner, robust way to ask this question?
187+
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
188+
// and that doesn't get us the true name in scope rather than the span text either.
189+
if let Some(name) = snippet_opt(cx, local_span)
190+
&& is_ident(&name)
191+
{
192+
// If the local is an ordinary named variable,
193+
// print its name rather than relying solely on the span.
194+
diag.span_label(
195+
local_span,
196+
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
197+
);
198+
} else {
199+
diag.span_label(
200+
local_span,
201+
format!("this is the largest part, at {size} for type `{ty}`"),
202+
);
203+
}
204+
}
205+
206+
// Explain why we are linting this and not other functions.
207+
diag.note(format!(
208+
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
209+
));
210+
211+
// Explain why the user should care, briefly.
212+
diag.note_once(
213+
"allocating large amounts of stack space can overflow the stack \
214+
and cause the program to abort",
215+
);
216+
},
157217
);
158218
}
159219
}

tests/ui-toml/large_stack_frames/large_stack_frames.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn f() {
1010
let _x = create_array::<1000>();
1111
}
1212
fn f2() {
13-
//~^ ERROR: this function allocates a large amount of stack space
13+
//~^ ERROR: this function may allocate 1001 bytes on the stack
1414
let _x = create_array::<1001>();
1515
}
1616

tests/ui-toml/large_stack_frames/large_stack_frames.stderr

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
error: this function allocates a large amount of stack space
2-
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:1
1+
error: this function may allocate 1001 bytes on the stack
2+
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:4
33
|
4-
LL | / fn f2() {
5-
LL | |
6-
LL | | let _x = create_array::<1001>();
7-
LL | | }
8-
| |_^
4+
LL | fn f2() {
5+
| ^^
6+
LL |
7+
LL | let _x = create_array::<1001>();
8+
| -- `_x` is the largest part, at 1001 bytes for type `[u8; 1001]`
99
|
10-
= note: allocating large amounts of stack space can overflow the stack
10+
= note: 1001 bytes is larger than Clippy's configured `stack-size-threshold` of 1000
11+
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
1112
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
1213
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
1314

tests/ui/large_stack_frames.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//@ normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR"
2+
//@ normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR"
13
#![allow(unused, incomplete_features)]
24
#![warn(clippy::large_stack_frames)]
35
#![feature(unsized_locals)]
@@ -23,8 +25,7 @@ impl<const N: usize> Default for ArrayDefault<N> {
2325
}
2426

2527
fn many_small_arrays() {
26-
//~^ ERROR: this function allocates a large amount of stack space
27-
//~| NOTE: allocating large amounts of stack space can overflow the stack
28+
//~^ ERROR: this function may allocate
2829
let x = [0u8; 500_000];
2930
let x2 = [0u8; 500_000];
3031
let x3 = [0u8; 500_000];
@@ -34,17 +35,21 @@ fn many_small_arrays() {
3435
}
3536

3637
fn large_return_value() -> ArrayDefault<1_000_000> {
37-
//~^ ERROR: this function allocates a large amount of stack space
38-
//~| NOTE: allocating large amounts of stack space can overflow the stack
38+
//~^ ERROR: this function may allocate 1000000 bytes on the stack
3939
Default::default()
4040
}
4141

4242
fn large_fn_arg(x: ArrayDefault<1_000_000>) {
43-
//~^ ERROR: this function allocates a large amount of stack space
44-
//~| NOTE: allocating large amounts of stack space can overflow the stack
43+
//~^ ERROR: this function may allocate
4544
black_box(&x);
4645
}
4746

47+
fn has_large_closure() {
48+
let f = || black_box(&[0u8; 1_000_000]);
49+
//~^ ERROR: this function may allocate
50+
f();
51+
}
52+
4853
fn main() {
4954
generic::<ArrayDefault<1_000_000>>();
5055
}

tests/ui/large_stack_frames.stderr

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,42 @@
1-
error: this function allocates a large amount of stack space
2-
--> tests/ui/large_stack_frames.rs:25:1
3-
|
4-
LL | / fn many_small_arrays() {
5-
LL | |
6-
LL | |
7-
LL | | let x = [0u8; 500_000];
8-
... |
9-
LL | | black_box((&x, &x2, &x3, &x4, &x5));
10-
LL | | }
11-
| |_^
12-
|
13-
= note: allocating large amounts of stack space can overflow the stack
1+
error: this function may allocate 250$PTR bytes on the stack
2+
--> tests/ui/large_stack_frames.rs:27:4
3+
|
4+
LL | fn many_small_arrays() {
5+
| ^^^^^^^^^^^^^^^^^
6+
...
7+
LL | let x5 = [0u8; 500_000];
8+
| -- `x5` is the largest part, at 500000 bytes for type `[u8; 500000]`
9+
|
10+
= note: 250$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
11+
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
1412
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
1513
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
1614

17-
error: this function allocates a large amount of stack space
18-
--> tests/ui/large_stack_frames.rs:36:1
15+
error: this function may allocate 1000000 bytes on the stack
16+
--> tests/ui/large_stack_frames.rs:37:4
17+
|
18+
LL | fn large_return_value() -> ArrayDefault<1_000_000> {
19+
| ^^^^^^^^^^^^^^^^^^ ----------------------- this is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
20+
|
21+
= note: 1000000 bytes is larger than Clippy's configured `stack-size-threshold` of 512000
22+
23+
error: this function may allocate 100$PTR bytes on the stack
24+
--> tests/ui/large_stack_frames.rs:42:4
1925
|
20-
LL | / fn large_return_value() -> ArrayDefault<1_000_000> {
21-
LL | |
22-
LL | |
23-
LL | | Default::default()
24-
LL | | }
25-
| |_^
26+
LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) {
27+
| ^^^^^^^^^^^^ - `x` is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
2628
|
27-
= note: allocating large amounts of stack space can overflow the stack
29+
= note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
2830

29-
error: this function allocates a large amount of stack space
30-
--> tests/ui/large_stack_frames.rs:42:1
31+
error: this function may allocate 100$PTR bytes on the stack
32+
--> tests/ui/large_stack_frames.rs:48:13
3133
|
32-
LL | / fn large_fn_arg(x: ArrayDefault<1_000_000>) {
33-
LL | |
34-
LL | |
35-
LL | | black_box(&x);
36-
LL | | }
37-
| |_^
34+
LL | let f = || black_box(&[0u8; 1_000_000]);
35+
| ^^^^^^^^^^^^^^----------------^
36+
| |
37+
| this is the largest part, at 1000000 bytes for type `[u8; 1000000]`
3838
|
39-
= note: allocating large amounts of stack space can overflow the stack
39+
= note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
4040

41-
error: aborting due to 3 previous errors
41+
error: aborting due to 4 previous errors
4242

0 commit comments

Comments
 (0)