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)
This commit is contained in:
parent
124e68bef8
commit
01646457a9
@ -1,10 +1,12 @@
|
||||
use std::ops::AddAssign;
|
||||
use std::{fmt, ops};
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_note;
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use clippy_utils::fn_has_unsatisfiable_preds;
|
||||
use clippy_utils::source::snippet_opt;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::{Body, FnDecl};
|
||||
use rustc_lexer::is_ident;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
@ -108,13 +110,25 @@ pub fn exceeds_limit(self, limit: u64) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<u64> for Space {
|
||||
fn add_assign(&mut self, rhs: u64) {
|
||||
if let Self::Used(lhs) = self {
|
||||
match lhs.checked_add(rhs) {
|
||||
Some(sum) => *self = Self::Used(sum),
|
||||
None => *self = Self::Overflow,
|
||||
}
|
||||
impl fmt::Display for Space {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Space::Used(1) => write!(f, "1 byte"),
|
||||
Space::Used(n) => write!(f, "{n} bytes"),
|
||||
Space::Overflow => write!(f, "over 2⁶⁴-1 bytes"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<u64> for Space {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: u64) -> Self {
|
||||
match self {
|
||||
Self::Used(lhs) => match lhs.checked_add(rhs) {
|
||||
Some(sum) => Self::Used(sum),
|
||||
None => Self::Overflow,
|
||||
},
|
||||
Self::Overflow => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,10 +137,10 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
_: FnKind<'tcx>,
|
||||
fn_kind: FnKind<'tcx>,
|
||||
_: &'tcx FnDecl<'tcx>,
|
||||
_: &'tcx Body<'tcx>,
|
||||
span: Span,
|
||||
entire_fn_span: Span,
|
||||
local_def_id: LocalDefId,
|
||||
) {
|
||||
let def_id = local_def_id.to_def_id();
|
||||
@ -138,22 +152,68 @@ fn check_fn(
|
||||
let mir = cx.tcx.optimized_mir(def_id);
|
||||
let param_env = cx.tcx.param_env(def_id);
|
||||
|
||||
let mut frame_size = Space::Used(0);
|
||||
let sizes_of_locals = || {
|
||||
mir.local_decls.iter().filter_map(|local| {
|
||||
let layout = cx.tcx.layout_of(param_env.and(local.ty)).ok()?;
|
||||
Some((local, layout.size.bytes()))
|
||||
})
|
||||
};
|
||||
|
||||
for local in &mir.local_decls {
|
||||
if let Ok(layout) = cx.tcx.layout_of(param_env.and(local.ty)) {
|
||||
frame_size += layout.size.bytes();
|
||||
}
|
||||
}
|
||||
let frame_size = sizes_of_locals().fold(Space::Used(0), |sum, (_, size)| sum + size);
|
||||
|
||||
if frame_size.exceeds_limit(self.maximum_allowed_size) {
|
||||
span_lint_and_note(
|
||||
let limit = self.maximum_allowed_size;
|
||||
if frame_size.exceeds_limit(limit) {
|
||||
// Point at just the function name if possible, because lints that span
|
||||
// the entire body and don't have to are less legible.
|
||||
let fn_span = match fn_kind {
|
||||
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
|
||||
FnKind::Closure => entire_fn_span,
|
||||
};
|
||||
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_STACK_FRAMES,
|
||||
span,
|
||||
"this function allocates a large amount of stack space",
|
||||
None,
|
||||
"allocating large amounts of stack space can overflow the stack",
|
||||
fn_span,
|
||||
&format!("this function may allocate {frame_size} on the stack"),
|
||||
|diag| {
|
||||
// Point out the largest individual contribution to this size, because
|
||||
// it is the most likely to be unintentionally large.
|
||||
if let Some((local, size)) = sizes_of_locals().max_by_key(|&(_, size)| size) {
|
||||
let local_span: Span = local.source_info.span;
|
||||
let size = Space::Used(size); // pluralizes for us
|
||||
let ty = local.ty;
|
||||
|
||||
// TODO: Is there a cleaner, robust way to ask this question?
|
||||
// The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
|
||||
// and that doesn't get us the true name in scope rather than the span text either.
|
||||
if let Some(name) = snippet_opt(cx, local_span)
|
||||
&& is_ident(&name)
|
||||
{
|
||||
// If the local is an ordinary named variable,
|
||||
// print its name rather than relying solely on the span.
|
||||
diag.span_label(
|
||||
local_span,
|
||||
format!("`{name}` is the largest part, at {size} for type `{ty}`"),
|
||||
);
|
||||
} else {
|
||||
diag.span_label(
|
||||
local_span,
|
||||
format!("this is the largest part, at {size} for type `{ty}`"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Explain why we are linting this and not other functions.
|
||||
diag.note(format!(
|
||||
"{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
|
||||
));
|
||||
|
||||
// Explain why the user should care, briefly.
|
||||
diag.note_once(
|
||||
"allocating large amounts of stack space can overflow the stack \
|
||||
and cause the program to abort",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ fn f() {
|
||||
let _x = create_array::<1000>();
|
||||
}
|
||||
fn f2() {
|
||||
//~^ ERROR: this function allocates a large amount of stack space
|
||||
//~^ ERROR: this function may allocate 1001 bytes on the stack
|
||||
let _x = create_array::<1001>();
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
error: this function allocates a large amount of stack space
|
||||
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:1
|
||||
error: this function may allocate 1001 bytes on the stack
|
||||
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:4
|
||||
|
|
||||
LL | / fn f2() {
|
||||
LL | |
|
||||
LL | | let _x = create_array::<1001>();
|
||||
LL | | }
|
||||
| |_^
|
||||
LL | fn f2() {
|
||||
| ^^
|
||||
LL |
|
||||
LL | let _x = create_array::<1001>();
|
||||
| -- `_x` is the largest part, at 1001 bytes for type `[u8; 1001]`
|
||||
|
|
||||
= note: allocating large amounts of stack space can overflow the stack
|
||||
= note: 1001 bytes is larger than Clippy's configured `stack-size-threshold` of 1000
|
||||
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
|
||||
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
//@ normalize-stderr-test: "\b10000(08|16|32)\b" -> "100$$PTR"
|
||||
//@ normalize-stderr-test: "\b2500(060|120)\b" -> "250$$PTR"
|
||||
#![allow(unused, incomplete_features)]
|
||||
#![warn(clippy::large_stack_frames)]
|
||||
#![feature(unsized_locals)]
|
||||
@ -23,8 +25,7 @@ fn default() -> Self {
|
||||
}
|
||||
|
||||
fn many_small_arrays() {
|
||||
//~^ ERROR: this function allocates a large amount of stack space
|
||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
||||
//~^ ERROR: this function may allocate
|
||||
let x = [0u8; 500_000];
|
||||
let x2 = [0u8; 500_000];
|
||||
let x3 = [0u8; 500_000];
|
||||
@ -34,17 +35,21 @@ fn many_small_arrays() {
|
||||
}
|
||||
|
||||
fn large_return_value() -> ArrayDefault<1_000_000> {
|
||||
//~^ ERROR: this function allocates a large amount of stack space
|
||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
||||
//~^ ERROR: this function may allocate 1000000 bytes on the stack
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
||||
//~^ ERROR: this function allocates a large amount of stack space
|
||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
||||
//~^ ERROR: this function may allocate
|
||||
black_box(&x);
|
||||
}
|
||||
|
||||
fn has_large_closure() {
|
||||
let f = || black_box(&[0u8; 1_000_000]);
|
||||
//~^ ERROR: this function may allocate
|
||||
f();
|
||||
}
|
||||
|
||||
fn main() {
|
||||
generic::<ArrayDefault<1_000_000>>();
|
||||
}
|
||||
|
@ -1,42 +1,42 @@
|
||||
error: this function allocates a large amount of stack space
|
||||
--> tests/ui/large_stack_frames.rs:25:1
|
||||
error: this function may allocate 250$PTR bytes on the stack
|
||||
--> tests/ui/large_stack_frames.rs:27:4
|
||||
|
|
||||
LL | / fn many_small_arrays() {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | | let x = [0u8; 500_000];
|
||||
... |
|
||||
LL | | black_box((&x, &x2, &x3, &x4, &x5));
|
||||
LL | | }
|
||||
| |_^
|
||||
LL | fn many_small_arrays() {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
LL | let x5 = [0u8; 500_000];
|
||||
| -- `x5` is the largest part, at 500000 bytes for type `[u8; 500000]`
|
||||
|
|
||||
= note: allocating large amounts of stack space can overflow the stack
|
||||
= note: 250$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
|
||||
= note: allocating large amounts of stack space can overflow the stack and cause the program to abort
|
||||
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
|
||||
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
||||
|
||||
error: this function allocates a large amount of stack space
|
||||
--> tests/ui/large_stack_frames.rs:36:1
|
||||
error: this function may allocate 1000000 bytes on the stack
|
||||
--> tests/ui/large_stack_frames.rs:37:4
|
||||
|
|
||||
LL | / fn large_return_value() -> ArrayDefault<1_000_000> {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | | Default::default()
|
||||
LL | | }
|
||||
| |_^
|
||||
LL | fn large_return_value() -> ArrayDefault<1_000_000> {
|
||||
| ^^^^^^^^^^^^^^^^^^ ----------------------- this is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
|
||||
|
|
||||
= note: allocating large amounts of stack space can overflow the stack
|
||||
= note: 1000000 bytes is larger than Clippy's configured `stack-size-threshold` of 512000
|
||||
|
||||
error: this function allocates a large amount of stack space
|
||||
--> tests/ui/large_stack_frames.rs:42:1
|
||||
error: this function may allocate 100$PTR bytes on the stack
|
||||
--> tests/ui/large_stack_frames.rs:42:4
|
||||
|
|
||||
LL | / fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
||||
LL | |
|
||||
LL | |
|
||||
LL | | black_box(&x);
|
||||
LL | | }
|
||||
| |_^
|
||||
LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
||||
| ^^^^^^^^^^^^ - `x` is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
|
||||
|
|
||||
= note: allocating large amounts of stack space can overflow the stack
|
||||
= note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: this function may allocate 100$PTR bytes on the stack
|
||||
--> tests/ui/large_stack_frames.rs:48:13
|
||||
|
|
||||
LL | let f = || black_box(&[0u8; 1_000_000]);
|
||||
| ^^^^^^^^^^^^^^----------------^
|
||||
| |
|
||||
| this is the largest part, at 1000000 bytes for type `[u8; 1000000]`
|
||||
|
|
||||
= note: 100$PTR bytes is larger than Clippy's configured `stack-size-threshold` of 512000
|
||||
|
||||
error: aborting due to 4 previous errors
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user