mirror of
https://github.com/rust-lang/rust-clippy
synced 2024-11-26 22:50:56 +00:00
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
5 changed files with 134 additions and 68 deletions
|
@ -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::fn_has_unsatisfiable_preds;
|
||||||
|
use clippy_utils::source::snippet_opt;
|
||||||
use rustc_hir::def_id::LocalDefId;
|
use rustc_hir::def_id::LocalDefId;
|
||||||
use rustc_hir::intravisit::FnKind;
|
use rustc_hir::intravisit::FnKind;
|
||||||
use rustc_hir::{Body, FnDecl};
|
use rustc_hir::{Body, FnDecl};
|
||||||
|
use rustc_lexer::is_ident;
|
||||||
use rustc_lint::{LateContext, LateLintPass};
|
use rustc_lint::{LateContext, LateLintPass};
|
||||||
use rustc_session::impl_lint_pass;
|
use rustc_session::impl_lint_pass;
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
@ -108,13 +110,25 @@ impl Space {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign<u64> for Space {
|
impl fmt::Display for Space {
|
||||||
fn add_assign(&mut self, rhs: u64) {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Self::Used(lhs) = self {
|
match self {
|
||||||
match lhs.checked_add(rhs) {
|
Space::Used(1) => write!(f, "1 byte"),
|
||||||
Some(sum) => *self = Self::Used(sum),
|
Space::Used(n) => write!(f, "{n} bytes"),
|
||||||
None => *self = Self::Overflow,
|
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(
|
fn check_fn(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &LateContext<'tcx>,
|
cx: &LateContext<'tcx>,
|
||||||
_: FnKind<'tcx>,
|
fn_kind: FnKind<'tcx>,
|
||||||
_: &'tcx FnDecl<'tcx>,
|
_: &'tcx FnDecl<'tcx>,
|
||||||
_: &'tcx Body<'tcx>,
|
_: &'tcx Body<'tcx>,
|
||||||
span: Span,
|
entire_fn_span: Span,
|
||||||
local_def_id: LocalDefId,
|
local_def_id: LocalDefId,
|
||||||
) {
|
) {
|
||||||
let def_id = local_def_id.to_def_id();
|
let def_id = local_def_id.to_def_id();
|
||||||
|
@ -138,22 +152,68 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
|
||||||
let mir = cx.tcx.optimized_mir(def_id);
|
let mir = cx.tcx.optimized_mir(def_id);
|
||||||
let param_env = cx.tcx.param_env(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 {
|
let frame_size = sizes_of_locals().fold(Space::Used(0), |sum, (_, size)| sum + size);
|
||||||
if let Ok(layout) = cx.tcx.layout_of(param_env.and(local.ty)) {
|
|
||||||
frame_size += layout.size.bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if frame_size.exceeds_limit(self.maximum_allowed_size) {
|
let limit = self.maximum_allowed_size;
|
||||||
span_lint_and_note(
|
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,
|
cx,
|
||||||
LARGE_STACK_FRAMES,
|
LARGE_STACK_FRAMES,
|
||||||
span,
|
fn_span,
|
||||||
"this function allocates a large amount of stack space",
|
&format!("this function may allocate {frame_size} on the stack"),
|
||||||
None,
|
|diag| {
|
||||||
"allocating large amounts of stack space can overflow the stack",
|
// 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>();
|
let _x = create_array::<1000>();
|
||||||
}
|
}
|
||||||
fn f2() {
|
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>();
|
let _x = create_array::<1001>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
error: this function allocates a large amount of stack space
|
error: this function may allocate 1001 bytes on the stack
|
||||||
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:1
|
--> tests/ui-toml/large_stack_frames/large_stack_frames.rs:12:4
|
||||||
|
|
|
|
||||||
LL | / fn f2() {
|
LL | fn f2() {
|
||||||
LL | |
|
| ^^
|
||||||
LL | | let _x = create_array::<1001>();
|
LL |
|
||||||
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`
|
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
|
||||||
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
= 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)]
|
#![allow(unused, incomplete_features)]
|
||||||
#![warn(clippy::large_stack_frames)]
|
#![warn(clippy::large_stack_frames)]
|
||||||
#![feature(unsized_locals)]
|
#![feature(unsized_locals)]
|
||||||
|
@ -23,8 +25,7 @@ impl<const N: usize> Default for ArrayDefault<N> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn many_small_arrays() {
|
fn many_small_arrays() {
|
||||||
//~^ ERROR: this function allocates a large amount of stack space
|
//~^ ERROR: this function may allocate
|
||||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
|
||||||
let x = [0u8; 500_000];
|
let x = [0u8; 500_000];
|
||||||
let x2 = [0u8; 500_000];
|
let x2 = [0u8; 500_000];
|
||||||
let x3 = [0u8; 500_000];
|
let x3 = [0u8; 500_000];
|
||||||
|
@ -34,17 +35,21 @@ fn many_small_arrays() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn large_return_value() -> ArrayDefault<1_000_000> {
|
fn large_return_value() -> ArrayDefault<1_000_000> {
|
||||||
//~^ ERROR: this function allocates a large amount of stack space
|
//~^ ERROR: this function may allocate 1000000 bytes on the stack
|
||||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
|
||||||
Default::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
||||||
//~^ ERROR: this function allocates a large amount of stack space
|
//~^ ERROR: this function may allocate
|
||||||
//~| NOTE: allocating large amounts of stack space can overflow the stack
|
|
||||||
black_box(&x);
|
black_box(&x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_large_closure() {
|
||||||
|
let f = || black_box(&[0u8; 1_000_000]);
|
||||||
|
//~^ ERROR: this function may allocate
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
generic::<ArrayDefault<1_000_000>>();
|
generic::<ArrayDefault<1_000_000>>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
error: this function allocates a large amount of stack space
|
error: this function may allocate 250$PTR bytes on the stack
|
||||||
--> tests/ui/large_stack_frames.rs:25:1
|
--> tests/ui/large_stack_frames.rs:27:4
|
||||||
|
|
|
|
||||||
LL | / fn many_small_arrays() {
|
LL | fn many_small_arrays() {
|
||||||
LL | |
|
| ^^^^^^^^^^^^^^^^^
|
||||||
LL | |
|
...
|
||||||
LL | | let x = [0u8; 500_000];
|
LL | let x5 = [0u8; 500_000];
|
||||||
... |
|
| -- `x5` is the largest part, at 500000 bytes for type `[u8; 500000]`
|
||||||
LL | | black_box((&x, &x2, &x3, &x4, &x5));
|
|
||||||
LL | | }
|
|
||||||
| |_^
|
|
||||||
|
|
|
|
||||||
= 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`
|
= note: `-D clippy::large-stack-frames` implied by `-D warnings`
|
||||||
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
= help: to override `-D warnings` add `#[allow(clippy::large_stack_frames)]`
|
||||||
|
|
||||||
error: this function allocates a large amount of stack space
|
error: this function may allocate 1000000 bytes on the stack
|
||||||
--> tests/ui/large_stack_frames.rs:36:1
|
--> tests/ui/large_stack_frames.rs:37:4
|
||||||
|
|
|
|
||||||
LL | / fn large_return_value() -> ArrayDefault<1_000_000> {
|
LL | fn large_return_value() -> ArrayDefault<1_000_000> {
|
||||||
LL | |
|
| ^^^^^^^^^^^^^^^^^^ ----------------------- this is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
|
||||||
LL | |
|
|
||||||
LL | | Default::default()
|
|
||||||
LL | | }
|
|
||||||
| |_^
|
|
||||||
|
|
|
|
||||||
= 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
|
error: this function may allocate 100$PTR bytes on the stack
|
||||||
--> tests/ui/large_stack_frames.rs:42:1
|
--> tests/ui/large_stack_frames.rs:42:4
|
||||||
|
|
|
|
||||||
LL | / fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
LL | fn large_fn_arg(x: ArrayDefault<1_000_000>) {
|
||||||
LL | |
|
| ^^^^^^^^^^^^ - `x` is the largest part, at 1000000 bytes for type `ArrayDefault<1000000>`
|
||||||
LL | |
|
|
||||||
LL | | black_box(&x);
|
|
||||||
LL | | }
|
|
||||||
| |_^
|
|
||||||
|
|
|
|
||||||
= 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 a new issue