1- use std:: ops:: AddAssign ;
1+ use std:: { fmt , ops} ;
22
3- use clippy_utils:: diagnostics:: span_lint_and_note ;
3+ use clippy_utils:: diagnostics:: span_lint_and_then ;
44use clippy_utils:: fn_has_unsatisfiable_preds;
5+ use clippy_utils:: source:: snippet_opt;
56use rustc_hir:: def_id:: LocalDefId ;
67use rustc_hir:: intravisit:: FnKind ;
78use rustc_hir:: { Body , FnDecl } ;
9+ use rustc_lexer:: is_ident;
810use rustc_lint:: { LateContext , LateLintPass } ;
911use rustc_session:: impl_lint_pass;
1012use rustc_span:: Span ;
@@ -108,13 +110,25 @@ impl Space {
108110 }
109111}
110112
111- impl AddAssign < u64 > for Space {
112- fn add_assign ( & mut self , rhs : u64 ) {
113- if let Self :: Used ( lhs) = self {
114- match lhs. checked_add ( rhs) {
115- Some ( sum) => * self = Self :: Used ( sum) ,
116- None => * self = Self :: Overflow ,
117- }
113+ impl fmt:: Display for Space {
114+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
115+ match self {
116+ Space :: Used ( 1 ) => write ! ( f, "1 byte" ) ,
117+ Space :: Used ( n) => write ! ( f, "{n} bytes" ) ,
118+ Space :: Overflow => write ! ( f, "over 2⁶⁴-1 bytes" ) ,
119+ }
120+ }
121+ }
122+
123+ impl ops:: Add < u64 > for Space {
124+ type Output = Self ;
125+ fn add ( self , rhs : u64 ) -> Self {
126+ match self {
127+ Self :: Used ( lhs) => match lhs. checked_add ( rhs) {
128+ Some ( sum) => Self :: Used ( sum) ,
129+ None => Self :: Overflow ,
130+ } ,
131+ Self :: Overflow => self ,
118132 }
119133 }
120134}
@@ -123,10 +137,10 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
123137 fn check_fn (
124138 & mut self ,
125139 cx : & LateContext < ' tcx > ,
126- _ : FnKind < ' tcx > ,
140+ fn_kind : FnKind < ' tcx > ,
127141 _: & ' tcx FnDecl < ' tcx > ,
128142 _: & ' tcx Body < ' tcx > ,
129- span : Span ,
143+ entire_fn_span : Span ,
130144 local_def_id : LocalDefId ,
131145 ) {
132146 let def_id = local_def_id. to_def_id ( ) ;
@@ -138,22 +152,68 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
138152 let mir = cx. tcx . optimized_mir ( def_id) ;
139153 let param_env = cx. tcx . param_env ( def_id) ;
140154
141- let mut frame_size = Space :: Used ( 0 ) ;
155+ let sizes_of_locals = || {
156+ mir. local_decls . iter ( ) . filter_map ( |local| {
157+ let layout = cx. tcx . layout_of ( param_env. and ( local. ty ) ) . ok ( ) ?;
158+ Some ( ( local, layout. size . bytes ( ) ) )
159+ } )
160+ } ;
142161
143- for local in & mir. local_decls {
144- if let Ok ( layout) = cx. tcx . layout_of ( param_env. and ( local. ty ) ) {
145- frame_size += layout. size . bytes ( ) ;
146- }
147- }
162+ let frame_size = sizes_of_locals ( ) . fold ( Space :: Used ( 0 ) , |sum, ( _, size) | sum + size) ;
163+
164+ let limit = self . maximum_allowed_size ;
165+ if frame_size. exceeds_limit ( limit) {
166+ // Point at just the function name if possible, because lints that span
167+ // 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+ } ;
148172
149- if frame_size. exceeds_limit ( self . maximum_allowed_size ) {
150- span_lint_and_note (
173+ span_lint_and_then (
151174 cx,
152175 LARGE_STACK_FRAMES ,
153- span,
154- "this function allocates a large amount of stack space" ,
155- None ,
156- "allocating large amounts of stack space can overflow the stack" ,
176+ fn_span,
177+ & format ! ( "this function may allocate {frame_size} on the stack" ) ,
178+ |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) = snippet_opt ( cx, local_span)
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+ ) ;
216+ } ,
157217 ) ;
158218 }
159219 }
0 commit comments