new lint: large_stack_frames
This commit is contained in:
parent
a3b185b60a
commit
7312a93a06
9 changed files with 262 additions and 1 deletions
162
clippy_lints/src/large_stack_frames.rs
Normal file
162
clippy_lints/src/large_stack_frames.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use std::ops::AddAssign;
|
||||
|
||||
use clippy_utils::diagnostics::span_lint_and_then;
|
||||
use rustc_hir::def_id::LocalDefId;
|
||||
use rustc_hir::intravisit::FnKind;
|
||||
use rustc_hir::Body;
|
||||
use rustc_hir::FnDecl;
|
||||
use rustc_lint::{LateContext, LateLintPass};
|
||||
use rustc_session::declare_tool_lint;
|
||||
use rustc_session::impl_lint_pass;
|
||||
use rustc_span::Span;
|
||||
|
||||
declare_clippy_lint! {
|
||||
/// ### What it does
|
||||
/// Checks for functions that use a lot of stack space.
|
||||
///
|
||||
/// This often happens when constructing a large type, such as an array with a lot of elements,
|
||||
/// or constructing *many* smaller-but-still-large structs, or copying around a lot of large types.
|
||||
///
|
||||
/// This lint is a more general version of [`large_stack_arrays`](https://rust-lang.github.io/rust-clippy/master/#large_stack_arrays)
|
||||
/// that is intended to look at functions as a whole instead of only individual array expressions inside of a function.
|
||||
///
|
||||
/// ### Why is this bad?
|
||||
/// The stack region of memory is very limited in size (usually *much* smaller than the heap) and attempting to
|
||||
/// use too much will result in a stack overflow and crash the program.
|
||||
/// To avoid this, you should consider allocating large types on the heap instead (e.g. by boxing them).
|
||||
///
|
||||
/// Keep in mind that the code path to construction of large types does not even need to be reachable;
|
||||
/// it purely needs to *exist* inside of the function to contribute to the stack size.
|
||||
/// For example, this causes a stack overflow even though the branch is unreachable (with `-Zmir-opt-level=0`):
|
||||
/// ```rust,ignore
|
||||
/// fn main() {
|
||||
/// if false {
|
||||
/// let x = [0u8; 10000000]; // 10 MB stack array
|
||||
/// black_box(&x);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ### Drawbacks
|
||||
/// False positives. The stack size that clippy sees is an estimated value and can be vastly different
|
||||
/// from the actual stack usage after optimizations passes have run (especially true in release mode).
|
||||
/// Modern compilers are very smart and are able to optimize away a lot of unnecessary stack allocations.
|
||||
/// In debug mode however, it is usually more accurate.
|
||||
///
|
||||
/// This lint works by summing up the size of all locals and comparing them against a (configurable, but high-by-default)
|
||||
/// threshold.
|
||||
/// Note that "locals" in this context refers to [MIR locals](https://rustc-dev-guide.rust-lang.org/mir/index.html#key-mir-vocabulary),
|
||||
/// i.e. real local variables that the user typed, storage for temporary values, function arguments
|
||||
/// and the return value.
|
||||
///
|
||||
/// ### Example
|
||||
/// This function creates four 500 KB arrays on the stack. Quite big but just small enough to not trigger `large_stack_arrays`.
|
||||
/// However, looking at the function as a whole, it's clear that this uses a lot of stack space.
|
||||
/// ```rust
|
||||
/// struct QuiteLargeType([u8; 500_000]);
|
||||
/// fn foo() {
|
||||
/// // ... some function that uses a lot of stack space ...
|
||||
/// let _x1 = QuiteLargeType([0; 500_000]);
|
||||
/// let _x2 = QuiteLargeType([0; 500_000]);
|
||||
/// let _x3 = QuiteLargeType([0; 500_000]);
|
||||
/// let _x4 = QuiteLargeType([0; 500_000]);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Instead of doing this, allocate the arrays on the heap.
|
||||
/// This currently requires going through a `Vec` first and then converting it to a `Box`:
|
||||
/// ```rust
|
||||
/// struct NotSoLargeType(Box<[u8]>);
|
||||
///
|
||||
/// fn foo() {
|
||||
/// let _x1 = NotSoLargeType(vec![0; 500_000].into_boxed_slice());
|
||||
/// // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now heap allocated.
|
||||
/// // The size of `NotSoLargeType` is 16 bytes.
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
#[clippy::version = "1.71.0"]
|
||||
pub LARGE_STACK_FRAMES,
|
||||
nursery,
|
||||
"checks for functions that allocate a lot of stack space"
|
||||
}
|
||||
|
||||
pub struct LargeStackFrames {
|
||||
maximum_allowed_size: u64,
|
||||
}
|
||||
|
||||
impl LargeStackFrames {
|
||||
#[must_use]
|
||||
pub fn new(size: u64) -> Self {
|
||||
Self {
|
||||
maximum_allowed_size: size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_lint_pass!(LargeStackFrames => [LARGE_STACK_FRAMES]);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum Space {
|
||||
Used(u64),
|
||||
Overflow,
|
||||
}
|
||||
|
||||
impl Space {
|
||||
pub fn exceeds_limit(self, limit: u64) -> bool {
|
||||
match self {
|
||||
Self::Used(used) => used > limit,
|
||||
Self::Overflow => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<'tcx> LateLintPass<'tcx> for LargeStackFrames {
|
||||
fn check_fn(
|
||||
&mut self,
|
||||
cx: &LateContext<'tcx>,
|
||||
_: FnKind<'tcx>,
|
||||
_: &'tcx FnDecl<'tcx>,
|
||||
_: &'tcx Body<'tcx>,
|
||||
span: Span,
|
||||
local_def_id: LocalDefId,
|
||||
) {
|
||||
let def_id = local_def_id.to_def_id();
|
||||
|
||||
let mir = cx.tcx.optimized_mir(def_id);
|
||||
let param_env = cx.tcx.param_env(def_id);
|
||||
|
||||
let mut frame_size = Space::Used(0);
|
||||
|
||||
for local in &mir.local_decls {
|
||||
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) {
|
||||
span_lint_and_then(
|
||||
cx,
|
||||
LARGE_STACK_FRAMES,
|
||||
span,
|
||||
"this function allocates a large amount of stack space",
|
||||
|diag| {
|
||||
diag.note("allocating large amounts of stack space can overflow the stack");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue