@@ -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}
@@ -165,54 +168,105 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
165168 if frame_size. exceeds_limit ( limit) {
166169 // Point at just the function name if possible, because lints that span
167170 // 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,
171+ let ( fn_span, fn_name) = match fn_kind {
172+ FnKind :: ItemFn ( ident, _, _) => ( ident. span , format ! ( "function `{}`" , ident. name) ) ,
173+ FnKind :: Method ( ident, _) => ( ident. span , format ! ( "method `{}`" , ident. name) ) ,
174+ FnKind :: Closure => ( entire_fn_span, "closure" . to_string ( ) ) ,
171175 } ;
172176
177+ // Don't lint inside tests if configured to not do so.
178+ if self . allow_large_stack_frames_in_tests && is_in_test ( cx. tcx , cx. tcx . local_def_id_to_hir_id ( local_def_id) )
179+ {
180+ return ;
181+ }
182+
183+ let explain_lint = |diag : & mut Diag < ' _ , ( ) > , ctxt : SyntaxContext | {
184+ // Point out the largest individual contribution to this size, because
185+ // it is the most likely to be unintentionally large.
186+ if let Some ( ( local, size) ) = sizes_of_locals ( ) . max_by_key ( |& ( _, size) | size)
187+ && let local_span = local. source_info . span
188+ && local_span. ctxt ( ) == ctxt
189+ {
190+ let size = Space :: Used ( size) ; // pluralizes for us
191+ let ty = local. ty ;
192+
193+ // TODO: Is there a cleaner, robust way to ask this question?
194+ // The obvious `LocalDecl::is_user_variable()` panics on "unwrapping cross-crate data",
195+ // and that doesn't get us the true name in scope rather than the span text either.
196+ if let Some ( name) = local_span. get_source_text ( cx)
197+ && is_ident ( & name)
198+ {
199+ // If the local is an ordinary named variable,
200+ // print its name rather than relying solely on the span.
201+ diag. span_label (
202+ local_span,
203+ format ! ( "`{name}` is the largest part, at {size} for type `{ty}`" ) ,
204+ ) ;
205+ } else {
206+ diag. span_label (
207+ local_span,
208+ format ! ( "this is the largest part, at {size} for type `{ty}`" ) ,
209+ ) ;
210+ }
211+ }
212+
213+ // Explain why we are linting this and not other functions.
214+ diag. note ( format ! (
215+ "{frame_size} is larger than Clippy's configured `stack-size-threshold` of {limit}"
216+ ) ) ;
217+
218+ // Explain why the user should care, briefly.
219+ diag. note_once (
220+ "allocating large amounts of stack space can overflow the stack \
221+ and cause the program to abort",
222+ ) ;
223+ } ;
224+
225+ if fn_span. from_expansion ( ) {
226+ // Don't lint on the main function generated by `--test` target
227+ if cx. sess ( ) . is_test_crate ( ) && is_entrypoint_fn ( cx, local_def_id. to_def_id ( ) ) {
228+ return ;
229+ }
230+
231+ let is_from_external_macro = fn_span. in_external_macro ( cx. sess ( ) . source_map ( ) ) ;
232+ span_lint_and_then (
233+ cx,
234+ LARGE_STACK_FRAMES ,
235+ fn_span. source_callsite ( ) ,
236+ format ! (
237+ "{} generated by this macro may allocate a lot of stack space" ,
238+ if is_from_external_macro {
239+ cx. tcx. def_descr( local_def_id. into( ) )
240+ } else {
241+ fn_name. as_str( )
242+ }
243+ ) ,
244+ |diag| {
245+ if is_from_external_macro {
246+ return ;
247+ }
248+
249+ diag. span_label (
250+ fn_span,
251+ format ! (
252+ "this {} has a stack frame size of {frame_size}" ,
253+ cx. tcx. def_descr( local_def_id. into( ) )
254+ ) ,
255+ ) ;
256+
257+ explain_lint ( diag, fn_span. ctxt ( ) ) ;
258+ } ,
259+ ) ;
260+ return ;
261+ }
262+
173263 span_lint_and_then (
174264 cx,
175265 LARGE_STACK_FRAMES ,
176266 fn_span,
177267 format ! ( "this function may allocate {frame_size} on the stack" ) ,
178268 |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- ) ;
269+ explain_lint ( diag, SyntaxContext :: root ( ) ) ;
216270 } ,
217271 ) ;
218272 }
0 commit comments