15
15
//! The first delimiter, up to the `=>`, is the rule itself, and we look for
16
16
//! any `$ident:expr` metavariable declarations (implemented in
17
17
//! [`UnsafeMetavariableFinder::collect_expr_metavariables_in_rule`]).
18
+ //! All metavariables start out as [`MetavarState::Unreferenced`] at this point.
18
19
//!
19
20
//! Once we have those, we know the next token after the delimited rule is the `=>` and after that
20
21
//! we have the delimited macro rule body.
24
25
//! The metavariable finder stores a stack of [`EnclosingNode`]. The top always tells us if we're
25
26
//! within an unsafe block, and we push `EnclosingNode::UnsafeBlock` when we encounter `unsafe {`
26
27
//! (and pop from it at the right time when leaving the delimited block).
27
- //! So, if we find `$x` anywhere in the body and the last item is an unsafe block, emit a lint.
28
28
//!
29
- //! Note that it's possible to be inside of an unsafe block syntactically, but not semantically:
29
+ //! If we find a `$x` anywhere in the body and the last item is an unsafe block, this tells us that
30
+ //! this is likely something we want to lint, however we don't emit a warning right away,
31
+ //! but update the metavar to [`MetavarState::Referenced`].
32
+ //! Only at the end of the lint will we emit warnings for each `MetavarState::Referenced`.
33
+ //!
34
+ //! The reason for that is that it's possible that we might come across the same metavariable twice:
35
+ //! Once in an unsafe block, and then outside of one. There's no reason to lint that because the
36
+ //! user *does* need an unsafe block at call site to write unsafe code.
37
+ //! So if we do come across a metavar reference outside of an unsafe block, we have to update it to
38
+ //! `MetavarState::Suppressed` (indicating that this should never be linted or updated).
39
+ //!
40
+ //! Note also that it's possible to be inside of an unsafe block syntactically, but not
41
+ //! semantically:
30
42
//! ```ignore
31
43
//! macro_rules! x {
32
44
//! ($v:expr) => {
39
51
//! ```
40
52
//! The `$v` is in an unsafe block, but that is *not* considered by the unsafety checker because
41
53
//! it's part of another body.
42
- //! This is handled by having `EnclosingNode::Body`. In the above example, `:: Body` is at the top,
54
+ //! This is handled by having [ `EnclosingNode::Body`] . In the above example, `Body` is at the top,
43
55
//! so it's not linted.
44
56
//!
45
57
//! There are other edge cases for bodies (such as const generics, e.g. `Ty<{ $v > 3 }>`),
@@ -139,10 +151,38 @@ fn extract_metavariable<'a>(
139
151
}
140
152
}
141
153
142
- struct UnsafeMetavariableFinder < ' a , ' tcx > {
154
+ enum MetavarState {
155
+ /// The metavariable was referenced inside of an unsafe block
156
+ Referenced {
157
+ /// unsafe { $v }
158
+ /// ^^
159
+ reference_span : Span ,
160
+ /// macro_rules! x { (... $v:expr ...) => {} }
161
+ /// ^^^^^^^
162
+ decl_span : Span ,
163
+ /// unsafe { $v }
164
+ /// ^^^^^^^^
165
+ unsafe_bl_span : Span ,
166
+ } ,
167
+
168
+ /// The metavariable was not found yet, but we're still looking for it
169
+ Unreferenced { decl_span : Span } ,
170
+
171
+ /// The metavariable was found, but was then suppressed.
172
+ ///
173
+ /// For example:
174
+ /// ```ignore
175
+ /// unsafe { $v; }
176
+ /// ^^ Found reference to $v in unsafe block, set to Referenced
177
+ /// $v;
178
+ /// ^^ ... then later referenced outside of an `unsafe {}` block, set to Suppressed and avoid linting
179
+ /// ```
180
+ Suppressed ,
181
+ }
182
+
183
+ struct UnsafeMetavariableFinder {
143
184
enclosing : Vec < EnclosingNode > ,
144
- cx : & ' a LateContext < ' tcx > ,
145
- expr_variables : FxHashMap < Symbol , Span > ,
185
+ expr_variables : FxHashMap < Symbol , MetavarState > ,
146
186
}
147
187
148
188
enum EnclosingNode {
@@ -163,7 +203,7 @@ enum EnclosingNode {
163
203
UnsafeBlock ( Span ) ,
164
204
}
165
205
166
- impl < ' a , ' tcx > UnsafeMetavariableFinder < ' a , ' tcx > {
206
+ impl UnsafeMetavariableFinder {
167
207
fn clear ( & mut self ) {
168
208
self . expr_variables . clear ( ) ;
169
209
self . enclosing . clear ( ) ;
@@ -174,7 +214,7 @@ impl<'a, 'tcx> UnsafeMetavariableFinder<'a, 'tcx> {
174
214
/// macro_rules! x { (... $var:expr ...) => { ... } }
175
215
/// ^^^^^^^^^^^^^^^^^^^
176
216
/// ```
177
- /// Returns the variable `var`
217
+ /// Puts `var` in the variable map.
178
218
fn collect_expr_metavariables_in_rule ( & mut self , tts : & TokenStream ) {
179
219
let mut tts = tts. trees ( ) . peekable ( ) ;
180
220
while let Some ( tt) = tts. next ( ) {
@@ -195,7 +235,12 @@ impl<'a, 'tcx> UnsafeMetavariableFinder<'a, 'tcx> {
195
235
_,
196
236
) ) = tts. next ( )
197
237
{
198
- self . expr_variables . insert ( sym, start_span. to ( end_span) ) ;
238
+ self . expr_variables . insert (
239
+ sym,
240
+ MetavarState :: Unreferenced {
241
+ decl_span : start_span. to ( end_span) ,
242
+ } ,
243
+ ) ;
199
244
}
200
245
} ,
201
246
TokenTree :: Delimited ( .., body) => self . collect_expr_metavariables_in_rule ( body) ,
@@ -325,23 +370,29 @@ impl<'a, 'tcx> UnsafeMetavariableFinder<'a, 'tcx> {
325
370
self . find_metavariables_in_fn ( & mut tts) ;
326
371
}
327
372
} else if let Some ( ( sym, span) ) = extract_metavariable ( tt, & mut tts)
328
- && let Some ( decl_span) = self . expr_variables . get ( & sym) . copied ( )
329
- && let Some ( & EnclosingNode :: UnsafeBlock ( unsafe_bl_span) ) = self . enclosing . last ( )
373
+ && let Some ( state) = self . expr_variables . get_mut ( & sym)
330
374
{
331
- span_lint_and_then (
332
- self . cx ,
333
- EXPR_METAVARS_IN_UNSAFE ,
334
- span,
335
- "expanding an expression metavariable in an unsafe block" ,
336
- |diag| {
337
- diag. span_note ( decl_span, "metavariable declared here" ) ;
338
- diag. span_note ( unsafe_bl_span, "unsafe block entered here" ) ;
339
- diag. help (
340
- "consider expanding it outside of this block, e.g. by storing it in a variable" ,
341
- ) ;
342
- diag. note ( "this allows the user of the macro to write unsafe code without an unsafe block (at the callsite)" ) ;
375
+ let unsafe_block_span = self . enclosing . last ( ) . and_then ( |v| match v {
376
+ EnclosingNode :: Body => None ,
377
+ EnclosingNode :: UnsafeBlock ( span) => Some ( * span) ,
378
+ } ) ;
379
+
380
+ match ( & * state, unsafe_block_span) {
381
+ ( MetavarState :: Referenced { .. } , Some ( _) ) => { } ,
382
+ ( _, None ) => {
383
+ // Metavar was referenced outside of an unsafe block. Set to
384
+ // suppressed because we don't want to lint.
385
+ * state = MetavarState :: Suppressed ;
343
386
} ,
344
- ) ;
387
+ ( MetavarState :: Unreferenced { decl_span } , Some ( unsafe_bl_span) ) => {
388
+ * state = MetavarState :: Referenced {
389
+ reference_span : span,
390
+ decl_span : * decl_span,
391
+ unsafe_bl_span,
392
+ } ;
393
+ } ,
394
+ ( MetavarState :: Suppressed , _) => { } ,
395
+ }
345
396
}
346
397
} ,
347
398
TokenTree :: Delimited ( .., body) => self . find_metavariables_in_body ( body) ,
@@ -361,12 +412,12 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
361
412
if let ItemKind :: Macro ( def, MacroKind :: Bang ) = item. kind
362
413
&& let def_id = item. owner_id . def_id
363
414
&& ( cx. effective_visibilities . is_exported ( def_id) || cx. tcx . has_attr ( def_id, sym:: macro_export) )
415
+ && !cx. tcx . is_doc_hidden ( def_id)
364
416
{
365
417
let mut tts = def. body . tokens . trees ( ) . peekable ( ) ;
366
418
let mut finder = UnsafeMetavariableFinder {
367
419
enclosing : Vec :: new ( ) ,
368
420
expr_variables : FxHashMap :: default ( ) ,
369
- cx,
370
421
} ;
371
422
372
423
while let Some ( tt) = tts. next ( ) {
@@ -401,6 +452,30 @@ impl<'tcx> LateLintPass<'tcx> for ExprMetavarsInUnsafe {
401
452
. is_some ( )
402
453
{ }
403
454
}
455
+
456
+ for metavar in finder. expr_variables . values ( ) {
457
+ if let & MetavarState :: Referenced {
458
+ decl_span,
459
+ reference_span,
460
+ unsafe_bl_span,
461
+ } = metavar
462
+ {
463
+ span_lint_and_then (
464
+ cx,
465
+ EXPR_METAVARS_IN_UNSAFE ,
466
+ reference_span,
467
+ "expanding an expression metavariable in an unsafe block" ,
468
+ |diag| {
469
+ diag. span_note ( decl_span, "metavariable declared here" ) ;
470
+ diag. span_note ( unsafe_bl_span, "unsafe block entered here" ) ;
471
+ diag. help ( "consider expanding it outside of this block, e.g. by storing it in a variable" ) ;
472
+ diag. note (
473
+ "this allows the user of the macro to write unsafe code without an unsafe block (at the callsite)" ,
474
+ ) ;
475
+ } ,
476
+ ) ;
477
+ }
478
+ }
404
479
}
405
480
}
406
481
}
0 commit comments