1
- use std:: ops:: AddAssign ;
1
+ use std:: { fmt , ops} ;
2
2
3
- use clippy_utils:: diagnostics:: span_lint_and_note ;
3
+ use clippy_utils:: diagnostics:: span_lint_and_then ;
4
4
use clippy_utils:: fn_has_unsatisfiable_preds;
5
+ use clippy_utils:: source:: snippet_opt;
5
6
use rustc_hir:: def_id:: LocalDefId ;
6
7
use rustc_hir:: intravisit:: FnKind ;
7
8
use rustc_hir:: { Body , FnDecl } ;
9
+ use rustc_lexer:: is_ident;
8
10
use rustc_lint:: { LateContext , LateLintPass } ;
9
11
use rustc_session:: impl_lint_pass;
10
12
use rustc_span:: Span ;
@@ -108,13 +110,25 @@ impl Space {
108
110
}
109
111
}
110
112
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 ,
118
132
}
119
133
}
120
134
}
@@ -123,10 +137,10 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
123
137
fn check_fn (
124
138
& mut self ,
125
139
cx : & LateContext < ' tcx > ,
126
- _ : FnKind < ' tcx > ,
140
+ fn_kind : FnKind < ' tcx > ,
127
141
_: & ' tcx FnDecl < ' tcx > ,
128
142
_: & ' tcx Body < ' tcx > ,
129
- span : Span ,
143
+ entire_fn_span : Span ,
130
144
local_def_id : LocalDefId ,
131
145
) {
132
146
let def_id = local_def_id. to_def_id ( ) ;
@@ -138,22 +152,68 @@ impl<'tcx> LateLintPass<'tcx> for LargeStackFrames {
138
152
let mir = cx. tcx . optimized_mir ( def_id) ;
139
153
let param_env = cx. tcx . param_env ( def_id) ;
140
154
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
+ } ;
142
161
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
+ } ;
148
172
149
- if frame_size. exceeds_limit ( self . maximum_allowed_size ) {
150
- span_lint_and_note (
173
+ span_lint_and_then (
151
174
cx,
152
175
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
+ } ,
157
217
) ;
158
218
}
159
219
}
0 commit comments