@@ -2,15 +2,17 @@ use std::{fmt, ops};
22
33use clippy_config:: Conf ;
44use clippy_utils:: diagnostics:: span_lint_and_then;
5- use clippy_utils:: fn_has_unsatisfiable_preds;
6- use clippy_utils:: source:: SpanRangeExt ;
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 ;
79use rustc_hir:: def_id:: LocalDefId ;
810use rustc_hir:: intravisit:: FnKind ;
911use rustc_hir:: { Body , FnDecl } ;
1012use rustc_lexer:: is_ident;
1113use rustc_lint:: { LateContext , LateLintPass } ;
1214use rustc_session:: impl_lint_pass;
13- use rustc_span:: Span ;
15+ use rustc_span:: { MacroKind , Span , SyntaxContext } ;
1416
1517declare_clippy_lint ! {
1618 /// ### What it does
@@ -83,12 +85,14 @@ declare_clippy_lint! {
8385
8486pub struct LargeStackFrames {
8587 maximum_allowed_size : u64 ,
88+ allow_large_stack_frames_in_tests : bool ,
8689}
8790
8891impl LargeStackFrames {
8992 pub fn new ( conf : & ' static Conf ) -> Self {
9093 Self {
9194 maximum_allowed_size : conf. stack_size_threshold ,
95+ allow_large_stack_frames_in_tests : conf. allow_large_stack_frames_in_tests ,
9296 }
9397 }
9498}
@@ -165,54 +169,101 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
165169 if frame_size. exceeds_limit ( limit) {
166170 // Point at just the function name if possible, because lints that span
167171 // 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 ( ) ) ,
171176 } ;
172177
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+ span_lint_and_then (
233+ cx,
234+ LARGE_STACK_FRAMES ,
235+ fn_span. source_callsite ( ) ,
236+ format ! ( "{fn_name} generated by this macro may allocate a lot of stack space" ) ,
237+ |diag| {
238+ diag. span_label (
239+ fn_span,
240+ format ! (
241+ "this {} has a stack frame size of {frame_size}" ,
242+ cx. tcx. def_descr( local_def_id. into( ) )
243+ ) ,
244+ ) ;
245+
246+ if let Some ( MacroCall {
247+ kind : MacroKind :: Derive ,
248+ ..
249+ } ) = macro_backtrace ( fn_span) . last ( )
250+ {
251+ explain_lint ( diag, SyntaxContext :: root ( ) ) ;
252+ } else {
253+ explain_lint ( diag, fn_span. ctxt ( ) ) ;
254+ }
255+ } ,
256+ ) ;
257+ return ;
258+ }
259+
173260 span_lint_and_then (
174261 cx,
175262 LARGE_STACK_FRAMES ,
176263 fn_span,
177264 format ! ( "this function may allocate {frame_size} on the stack" ) ,
178265 |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- ) ;
266+ explain_lint ( diag, SyntaxContext :: root ( ) ) ;
216267 } ,
217268 ) ;
218269 }
0 commit comments