@@ -2,15 +2,16 @@ 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:: source:: { HasSession , SpanRangeExt } ;
6+ use clippy_utils:: { fn_has_unsatisfiable_preds, is_entrypoint_fn, is_in_test} ;
7+ use rustc_errors:: Diag ;
78use rustc_hir:: def_id:: LocalDefId ;
89use rustc_hir:: intravisit:: FnKind ;
910use rustc_hir:: { Body , FnDecl } ;
1011use rustc_lexer:: is_ident;
1112use rustc_lint:: { LateContext , LateLintPass } ;
1213use rustc_session:: impl_lint_pass;
13- use rustc_span:: Span ;
14+ use rustc_span:: { Span , SyntaxContext } ;
1415
1516declare_clippy_lint ! {
1617 /// ### What it does
@@ -83,12 +84,14 @@ declare_clippy_lint! {
8384
8485pub struct LargeStackFrames {
8586 maximum_allowed_size : u64 ,
87+ allow_large_stack_frames_in_tests : bool ,
8688}
8789
8890impl LargeStackFrames {
8991 pub fn new ( conf : & ' static Conf ) -> Self {
9092 Self {
9193 maximum_allowed_size : conf. stack_size_threshold ,
94+ allow_large_stack_frames_in_tests : conf. allow_large_stack_frames_in_tests ,
9295 }
9396 }
9497}
@@ -152,67 +155,122 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
152155 let mir = cx. tcx . optimized_mir ( def_id) ;
153156 let typing_env = mir. typing_env ( cx. tcx ) ;
154157
155- let sizes_of_locals = || {
156- mir. local_decls . iter ( ) . filter_map ( |local| {
158+ let sizes_of_locals = mir
159+ . local_decls
160+ . iter ( )
161+ . filter_map ( |local| {
157162 let layout = cx. tcx . layout_of ( typing_env. as_query_input ( local. ty ) ) . ok ( ) ?;
158163 Some ( ( local, layout. size . bytes ( ) ) )
159164 } )
160- } ;
165+ . collect :: < Vec < _ > > ( ) ;
161166
162- let frame_size = sizes_of_locals ( ) . fold ( Space :: Used ( 0 ) , |sum, ( _, size) | sum + size) ;
167+ let frame_size = sizes_of_locals
168+ . iter ( )
169+ . fold ( Space :: Used ( 0 ) , |sum, ( _, size) | sum + * size) ;
163170
164171 let limit = self . maximum_allowed_size ;
165172 if frame_size. exceeds_limit ( limit) {
166173 // Point at just the function name if possible, because lints that span
167174 // 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,
175+ let ( fn_span, fn_name) = match fn_kind {
176+ FnKind :: ItemFn ( ident, _, _) => ( ident. span , format ! ( "function `{}`" , ident. name) ) ,
177+ FnKind :: Method ( ident, _) => ( ident. span , format ! ( "method `{}`" , ident. name) ) ,
178+ FnKind :: Closure => ( entire_fn_span, "closure" . to_string ( ) ) ,
171179 } ;
172180
181+ // Don't lint inside tests if configured to not do so.
182+ if self . allow_large_stack_frames_in_tests && is_in_test ( cx. tcx , cx. tcx . local_def_id_to_hir_id ( local_def_id) )
183+ {
184+ return ;
185+ }
186+
187+ let explain_lint = |diag : & mut Diag < ' _ , ( ) > , ctxt : SyntaxContext | {
188+ // Point out the largest individual contribution to this size, because
189+ // it is the most likely to be unintentionally large.
190+ if let Some ( ( local, size) ) = sizes_of_locals. iter ( ) . max_by_key ( |& ( _, size) | size)
191+ && let local_span = local. source_info . span
192+ && local_span. ctxt ( ) == ctxt
193+ {
194+ let size = Space :: Used ( * size) ; // pluralizes for us
195+ let ty = local. ty ;
196+
197+ // TODO: Is there a cleaner, robust way to ask this question?
198+ // The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
199+ // and that doesn't get us the true name in scope rather than the span text either.
200+ if let Some ( name) = local_span. get_source_text ( cx)
201+ && is_ident ( & name)
202+ {
203+ // If the local is an ordinary named variable,
204+ // print its name rather than relying solely on the span.
205+ diag. span_label (
206+ local_span,
207+ format ! ( "`{name}` is the largest part, at {size} for type `{ty}`" ) ,
208+ ) ;
209+ } else {
210+ diag. span_label (
211+ local_span,
212+ format ! ( "this is the largest part, at {size} for type `{ty}`" ) ,
213+ ) ;
214+ }
215+ }
216+
217+ // Explain why we are linting this and not other functions.
218+ diag. note ( format ! (
219+ "{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
220+ ) ) ;
221+
222+ // Explain why the user should care, briefly.
223+ diag. note_once (
224+ "allocating large amounts of stack space can overflow the stack \
225+ and cause the program to abort",
226+ ) ;
227+ } ;
228+
229+ if fn_span. from_expansion ( ) {
230+ // Don't lint on the main function generated by `--test` target
231+ if cx. sess ( ) . is_test_crate ( ) && is_entrypoint_fn ( cx, local_def_id. to_def_id ( ) ) {
232+ return ;
233+ }
234+
235+ let is_from_external_macro = fn_span. in_external_macro ( cx. sess ( ) . source_map ( ) ) ;
236+ span_lint_and_then (
237+ cx,
238+ LARGE_STACK_FRAMES ,
239+ fn_span. source_callsite ( ) ,
240+ format ! (
241+ "{} generated by this macro may allocate a lot of stack space" ,
242+ if is_from_external_macro {
243+ cx. tcx. def_descr( local_def_id. into( ) )
244+ } else {
245+ fn_name. as_str( )
246+ }
247+ ) ,
248+ |diag| {
249+ if is_from_external_macro {
250+ return ;
251+ }
252+
253+ diag. span_label (
254+ fn_span,
255+ format ! (
256+ "this {} has a stack frame size of {frame_size}" ,
257+ cx. tcx. def_descr( local_def_id. into( ) )
258+ ) ,
259+ ) ;
260+
261+ explain_lint ( diag, fn_span. ctxt ( ) ) ;
262+ } ,
263+ ) ;
264+ return ;
265+ }
266+
173267 span_lint_and_then (
174268 cx,
175269 LARGE_STACK_FRAMES ,
176270 fn_span,
177271 format ! ( "this function may allocate {frame_size} on the stack" ) ,
178272 |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- ) ;
273+ explain_lint ( diag, SyntaxContext :: root ( ) ) ;
216274 } ,
217275 ) ;
218276 }
0 commit comments