|
1 | 1 | use std::{fmt, ops}; |
2 | 2 |
|
3 | 3 | use clippy_config::Conf; |
4 | | -use clippy_utils::diagnostics::span_lint_and_then; |
5 | | -use clippy_utils::fn_has_unsatisfiable_preds; |
6 | | -use clippy_utils::source::SpanRangeExt; |
| 4 | +use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; |
| 5 | +use clippy_utils::macros::{MacroCall, macro_backtrace}; |
| 6 | +use clippy_utils::source::{HasSession, SpanRangeExt}; |
| 7 | +use clippy_utils::{fn_has_unsatisfiable_preds, is_entrypoint_fn, is_in_test}; |
| 8 | +use rustc_errors::Diag; |
7 | 9 | use rustc_hir::def_id::LocalDefId; |
8 | 10 | use rustc_hir::intravisit::FnKind; |
9 | 11 | use rustc_hir::{Body, FnDecl}; |
10 | 12 | use rustc_lexer::is_ident; |
11 | 13 | use rustc_lint::{LateContext, LateLintPass}; |
12 | 14 | use rustc_session::impl_lint_pass; |
13 | | -use rustc_span::Span; |
| 15 | +use rustc_span::{MacroKind, Span, SyntaxContext}; |
14 | 16 |
|
15 | 17 | declare_clippy_lint! { |
16 | 18 | /// ### What it does |
@@ -83,12 +85,14 @@ declare_clippy_lint! { |
83 | 85 |
|
84 | 86 | pub struct LargeStackFrames { |
85 | 87 | maximum_allowed_size: u64, |
| 88 | + allow_large_stack_frames_in_tests: bool, |
86 | 89 | } |
87 | 90 |
|
88 | 91 | impl LargeStackFrames { |
89 | 92 | pub fn new(conf: &'static Conf) -> Self { |
90 | 93 | Self { |
91 | 94 | maximum_allowed_size: conf.stack_size_threshold, |
| 95 | + allow_large_stack_frames_in_tests: conf.allow_large_stack_frames_in_tests, |
92 | 96 | } |
93 | 97 | } |
94 | 98 | } |
@@ -165,54 +169,106 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames { |
165 | 169 | if frame_size.exceeds_limit(limit) { |
166 | 170 | // Point at just the function name if possible, because lints that span |
167 | 171 | // 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, |
| 172 | + let (fn_span, fn_name) = match fn_kind { |
| 173 | + FnKind::ItemFn(ident, _, _) => (ident.span, format!("function `{}`", ident.name)), |
| 174 | + FnKind::Method(ident, _) => (ident.span, format!("method `{}`", ident.name)), |
| 175 | + FnKind::Closure => (entire_fn_span, "closure".to_string()), |
171 | 176 | }; |
172 | 177 |
|
| 178 | + // Don't lint inside tests if configured to not do so. |
| 179 | + if self.allow_large_stack_frames_in_tests && is_in_test(cx.tcx, cx.tcx.local_def_id_to_hir_id(local_def_id)) |
| 180 | + { |
| 181 | + return; |
| 182 | + } |
| 183 | + |
| 184 | + let explain_lint = |diag: &mut Diag<'_, ()>, ctxt: SyntaxContext| { |
| 185 | + // Point out the largest individual contribution to this size, because |
| 186 | + // it is the most likely to be unintentionally large. |
| 187 | + if let Some((local, size)) = sizes_of_locals().max_by_key(|&(_, size)| size) |
| 188 | + && let local_span = local.source_info.span |
| 189 | + && local_span.ctxt() == ctxt |
| 190 | + { |
| 191 | + let size = Space::Used(size); // pluralizes for us |
| 192 | + let ty = local.ty; |
| 193 | + |
| 194 | + // TODO: Is there a cleaner, robust way to ask this question? |
| 195 | + // The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data", |
| 196 | + // and that doesn't get us the true name in scope rather than the span text either. |
| 197 | + if let Some(name) = local_span.get_source_text(cx) |
| 198 | + && is_ident(&name) |
| 199 | + { |
| 200 | + // If the local is an ordinary named variable, |
| 201 | + // print its name rather than relying solely on the span. |
| 202 | + diag.span_label( |
| 203 | + local_span, |
| 204 | + format!("`{name}` is the largest part, at {size} for type `{ty}`"), |
| 205 | + ); |
| 206 | + } else { |
| 207 | + diag.span_label( |
| 208 | + local_span, |
| 209 | + format!("this is the largest part, at {size} for type `{ty}`"), |
| 210 | + ); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + // Explain why we are linting this and not other functions. |
| 215 | + diag.note(format!( |
| 216 | + "{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}" |
| 217 | + )); |
| 218 | + |
| 219 | + // Explain why the user should care, briefly. |
| 220 | + diag.note_once( |
| 221 | + "allocating large amounts of stack space can overflow the stack \ |
| 222 | + and cause the program to abort", |
| 223 | + ); |
| 224 | + }; |
| 225 | + |
| 226 | + if fn_span.from_expansion() { |
| 227 | + // Don't lint on the main function generated by `--test` target |
| 228 | + if cx.sess().is_test_crate() && is_entrypoint_fn(cx, local_def_id.to_def_id()) { |
| 229 | + return; |
| 230 | + } |
| 231 | + |
| 232 | + if fn_span.in_external_macro(cx.sess().source_map()) { |
| 233 | + span_lint( |
| 234 | + cx, |
| 235 | + LARGE_STACK_FRAMES, |
| 236 | + fn_span.source_callsite(), |
| 237 | + format!( |
| 238 | + "{} generated by this macro may allocate a lot of stack space", |
| 239 | + cx.tcx.def_descr(local_def_id.into()) |
| 240 | + ), |
| 241 | + ); |
| 242 | + return; |
| 243 | + } |
| 244 | + |
| 245 | + span_lint_and_then( |
| 246 | + cx, |
| 247 | + LARGE_STACK_FRAMES, |
| 248 | + fn_span.source_callsite(), |
| 249 | + format!("{fn_name} generated by this macro may allocate a lot of stack space"), |
| 250 | + |diag| { |
| 251 | + diag.span_label( |
| 252 | + fn_span, |
| 253 | + format!( |
| 254 | + "this {} has a stack frame size of {frame_size}", |
| 255 | + cx.tcx.def_descr(local_def_id.into()) |
| 256 | + ), |
| 257 | + ); |
| 258 | + |
| 259 | + explain_lint(diag, fn_span.ctxt()); |
| 260 | + }, |
| 261 | + ); |
| 262 | + return; |
| 263 | + } |
| 264 | + |
173 | 265 | span_lint_and_then( |
174 | 266 | cx, |
175 | 267 | LARGE_STACK_FRAMES, |
176 | 268 | fn_span, |
177 | 269 | format!("this function may allocate {frame_size} on the stack"), |
178 | 270 | |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) = local_span.get_source_text(cx) |
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 | | - ); |
| 271 | + explain_lint(diag, SyntaxContext::root()); |
216 | 272 | }, |
217 | 273 | ); |
218 | 274 | } |
|
0 commit comments