1
1
use crate :: build:: ExprCategory ;
2
+ use hir:: intravisit;
3
+ use rustc_data_structures:: fx:: FxHashMap ;
2
4
use rustc_middle:: thir:: visit:: { self , Visitor } ;
3
5
4
6
use rustc_errors:: struct_span_err;
5
7
use rustc_hir as hir;
6
- use rustc_middle:: mir:: BorrowKind ;
7
- use rustc_middle:: thir:: * ;
8
+ use rustc_middle:: mir:: { BorrowKind , UnusedUnsafe , UsedUnsafeBlockData } ;
8
9
use rustc_middle:: ty:: { self , ParamEnv , Ty , TyCtxt } ;
10
+ use rustc_middle:: { lint, thir:: * } ;
9
11
use rustc_session:: lint:: builtin:: { UNSAFE_OP_IN_UNSAFE_FN , UNUSED_UNSAFE } ;
10
12
use rustc_session:: lint:: Level ;
11
13
use rustc_span:: def_id:: { DefId , LocalDefId } ;
12
14
use rustc_span:: symbol:: Symbol ;
13
15
use rustc_span:: Span ;
14
16
17
+ use std:: cmp;
18
+ use std:: collections:: hash_map;
15
19
use std:: ops:: Bound ;
16
20
17
21
struct UnsafetyVisitor < ' a , ' tcx > {
@@ -23,7 +27,6 @@ struct UnsafetyVisitor<'a, 'tcx> {
23
27
/// The current "safety context". This notably tracks whether we are in an
24
28
/// `unsafe` block, and whether it has been used.
25
29
safety_context : SafetyContext ,
26
- body_unsafety : BodyUnsafety ,
27
30
/// The `#[target_feature]` attributes of the body. Used for checking
28
31
/// calls to functions with `#[target_feature]` (RFC 2396).
29
32
body_target_features : & ' tcx Vec < Symbol > ,
@@ -33,52 +36,39 @@ struct UnsafetyVisitor<'a, 'tcx> {
33
36
in_union_destructure : bool ,
34
37
param_env : ParamEnv < ' tcx > ,
35
38
inside_adt : bool ,
39
+ used_unsafe_blocks : & ' a mut FxHashMap < hir:: HirId , UsedUnsafeBlockData > ,
36
40
}
37
41
38
42
impl < ' tcx > UnsafetyVisitor < ' _ , ' tcx > {
39
43
fn in_safety_context ( & mut self , safety_context : SafetyContext , f : impl FnOnce ( & mut Self ) ) {
40
- if let (
41
- SafetyContext :: UnsafeBlock { span : enclosing_span, .. } ,
42
- SafetyContext :: UnsafeBlock { span : block_span, hir_id, .. } ,
43
- ) = ( self . safety_context , safety_context)
44
- {
45
- self . warn_unused_unsafe (
46
- hir_id,
47
- block_span,
48
- Some ( ( self . tcx . sess . source_map ( ) . guess_head_span ( enclosing_span) , "block" ) ) ,
49
- ) ;
50
- f ( self ) ;
51
- } else {
52
- let prev_context = self . safety_context ;
53
- self . safety_context = safety_context;
44
+ let prev_context = self . safety_context ;
45
+ self . safety_context = safety_context;
54
46
55
- f ( self ) ;
47
+ f ( self ) ;
56
48
57
- if let SafetyContext :: UnsafeBlock { used : false , span, hir_id } = self . safety_context {
58
- self . warn_unused_unsafe (
59
- hir_id,
60
- span,
61
- if self . unsafe_op_in_unsafe_fn_allowed ( ) {
62
- self . body_unsafety . unsafe_fn_sig_span ( ) . map ( |span| ( span, "fn" ) )
63
- } else {
64
- None
65
- } ,
66
- ) ;
67
- }
68
- self . safety_context = prev_context;
69
- }
49
+ self . safety_context = prev_context;
70
50
}
71
51
72
52
fn requires_unsafe ( & mut self , span : Span , kind : UnsafeOpKind ) {
73
53
let ( description, note) = kind. description_and_note ( ) ;
74
54
let unsafe_op_in_unsafe_fn_allowed = self . unsafe_op_in_unsafe_fn_allowed ( ) ;
75
55
match self . safety_context {
76
56
SafetyContext :: BuiltinUnsafeBlock => { }
77
- SafetyContext :: UnsafeBlock { ref mut used, .. } => {
78
- if !self . body_unsafety . is_unsafe ( ) || !unsafe_op_in_unsafe_fn_allowed {
79
- // Mark this block as useful
80
- * used = true ;
81
- }
57
+ SafetyContext :: UnsafeBlock ( hir_id) => {
58
+ let new_usage = if unsafe_op_in_unsafe_fn_allowed {
59
+ UsedUnsafeBlockData :: AllAllowedInUnsafeFn ( self . hir_context )
60
+ } else {
61
+ UsedUnsafeBlockData :: SomeDisallowedInUnsafeFn
62
+ } ;
63
+ match self . used_unsafe_blocks . entry ( hir_id) {
64
+ hash_map:: Entry :: Occupied ( mut entry) => {
65
+ let entry = entry. get_mut ( ) ;
66
+ * entry = cmp:: min ( * entry, new_usage) ;
67
+ }
68
+ hash_map:: Entry :: Vacant ( entry) => {
69
+ entry. insert ( new_usage) ;
70
+ }
71
+ } ;
82
72
}
83
73
SafetyContext :: UnsafeFn if unsafe_op_in_unsafe_fn_allowed => { }
84
74
SafetyContext :: UnsafeFn => {
@@ -115,24 +105,6 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
115
105
}
116
106
}
117
107
118
- fn warn_unused_unsafe (
119
- & self ,
120
- hir_id : hir:: HirId ,
121
- block_span : Span ,
122
- enclosing_unsafe : Option < ( Span , & ' static str ) > ,
123
- ) {
124
- let block_span = self . tcx . sess . source_map ( ) . guess_head_span ( block_span) ;
125
- self . tcx . struct_span_lint_hir ( UNUSED_UNSAFE , hir_id, block_span, |lint| {
126
- let msg = "unnecessary `unsafe` block" ;
127
- let mut db = lint. build ( msg) ;
128
- db. span_label ( block_span, msg) ;
129
- if let Some ( ( span, kind) ) = enclosing_unsafe {
130
- db. span_label ( span, format ! ( "because it's nested under this `unsafe` {}" , kind) ) ;
131
- }
132
- db. emit ( ) ;
133
- } ) ;
134
- }
135
-
136
108
/// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
137
109
fn unsafe_op_in_unsafe_fn_allowed ( & self ) -> bool {
138
110
self . tcx . lint_level_at_node ( UNSAFE_OP_IN_UNSAFE_FN , self . hir_context ) . 0 == Level :: Allow
@@ -198,10 +170,9 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
198
170
} ) ;
199
171
}
200
172
BlockSafety :: ExplicitUnsafe ( hir_id) => {
201
- self . in_safety_context (
202
- SafetyContext :: UnsafeBlock { span : block. span , hir_id, used : false } ,
203
- |this| visit:: walk_block ( this, block) ,
204
- ) ;
173
+ self . in_safety_context ( SafetyContext :: UnsafeBlock ( hir_id) , |this| {
174
+ visit:: walk_block ( this, block)
175
+ } ) ;
205
176
}
206
177
BlockSafety :: Safe => {
207
178
visit:: walk_block ( self , block) ;
@@ -408,8 +379,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
408
379
let ( closure_thir, expr) = self . tcx . thir_body ( closure_def) ;
409
380
let closure_thir = & closure_thir. borrow ( ) ;
410
381
let hir_context = self . tcx . hir ( ) . local_def_id_to_hir_id ( closure_id) ;
411
- let mut closure_visitor =
412
- UnsafetyVisitor { thir : closure_thir, hir_context, ..* self } ;
382
+ let mut closure_visitor = UnsafetyVisitor {
383
+ thir : closure_thir,
384
+ hir_context,
385
+ used_unsafe_blocks : self . used_unsafe_blocks ,
386
+ ..* self
387
+ } ;
413
388
closure_visitor. visit_expr ( & closure_thir[ expr] ) ;
414
389
// Unsafe blocks can be used in closures, make sure to take it into account
415
390
self . safety_context = closure_visitor. safety_context ;
@@ -493,7 +468,7 @@ enum SafetyContext {
493
468
Safe ,
494
469
BuiltinUnsafeBlock ,
495
470
UnsafeFn ,
496
- UnsafeBlock { span : Span , hir_id : hir:: HirId , used : bool } ,
471
+ UnsafeBlock ( hir:: HirId ) ,
497
472
}
498
473
499
474
#[ derive( Clone , Copy ) ]
@@ -510,14 +485,6 @@ impl BodyUnsafety {
510
485
fn is_unsafe ( & self ) -> bool {
511
486
matches ! ( self , BodyUnsafety :: Unsafe ( _) )
512
487
}
513
-
514
- /// If the body is unsafe, returns the `Span` of its signature.
515
- fn unsafe_fn_sig_span ( self ) -> Option < Span > {
516
- match self {
517
- BodyUnsafety :: Unsafe ( span) => Some ( span) ,
518
- BodyUnsafety :: Safe => None ,
519
- }
520
- }
521
488
}
522
489
523
490
#[ derive( Clone , Copy , PartialEq ) ]
@@ -596,6 +563,111 @@ impl UnsafeOpKind {
596
563
}
597
564
}
598
565
566
+ /// Context information for [`UnusedUnsafeVisitor`] traversal,
567
+ /// saves (innermost) relevant context
568
+ #[ derive( Copy , Clone , Debug ) ]
569
+ enum Context {
570
+ Safe ,
571
+ /// in an `unsafe fn`
572
+ UnsafeFn ( hir:: HirId ) ,
573
+ /// in a *used* `unsafe` block
574
+ /// (i.e. a block without unused-unsafe warning)
575
+ UnsafeBlock ( hir:: HirId ) ,
576
+ }
577
+
578
+ struct UnusedUnsafeVisitor < ' a , ' tcx > {
579
+ tcx : TyCtxt < ' tcx > ,
580
+ used_unsafe_blocks : & ' a FxHashMap < hir:: HirId , UsedUnsafeBlockData > ,
581
+ context : Context ,
582
+ }
583
+
584
+ impl < ' tcx > intravisit:: Visitor < ' tcx > for UnusedUnsafeVisitor < ' _ , ' tcx > {
585
+ fn visit_block ( & mut self , block : & ' tcx hir:: Block < ' tcx > ) {
586
+ use UsedUnsafeBlockData :: { AllAllowedInUnsafeFn , SomeDisallowedInUnsafeFn } ;
587
+
588
+ if let hir:: BlockCheckMode :: UnsafeBlock ( hir:: UnsafeSource :: UserProvided ) = block. rules {
589
+ let used = match self . tcx . lint_level_at_node ( UNUSED_UNSAFE , block. hir_id ) {
590
+ ( Level :: Allow , _) => Some ( SomeDisallowedInUnsafeFn ) ,
591
+ _ => self . used_unsafe_blocks . get ( & block. hir_id ) . copied ( ) ,
592
+ } ;
593
+ // only reports if the contained `match` doesn't `return` early
594
+ report_unused_unsafe (
595
+ self . tcx ,
596
+ block. hir_id ,
597
+ match ( self . context , used) {
598
+ ( _, None ) => UnusedUnsafe :: Unused ,
599
+ ( Context :: Safe , Some ( _) )
600
+ | ( Context :: UnsafeFn ( _) , Some ( SomeDisallowedInUnsafeFn ) ) => {
601
+ let previous_context = self . context ;
602
+ self . context = Context :: UnsafeBlock ( block. hir_id ) ;
603
+ intravisit:: walk_block ( self , block) ;
604
+ self . context = previous_context;
605
+ return ;
606
+ }
607
+ ( Context :: UnsafeFn ( hir_id) , Some ( AllAllowedInUnsafeFn ( lint_root) ) ) => {
608
+ UnusedUnsafe :: InUnsafeFn ( hir_id, lint_root)
609
+ }
610
+ ( Context :: UnsafeBlock ( hir_id) , Some ( _) ) => UnusedUnsafe :: InUnsafeBlock ( hir_id) ,
611
+ } ,
612
+ ) ;
613
+ }
614
+ intravisit:: walk_block ( self , block) ;
615
+ }
616
+
617
+ fn visit_fn (
618
+ & mut self ,
619
+ fk : intravisit:: FnKind < ' tcx > ,
620
+ _fd : & ' tcx hir:: FnDecl < ' tcx > ,
621
+ b : hir:: BodyId ,
622
+ _s : rustc_span:: Span ,
623
+ _id : hir:: HirId ,
624
+ ) {
625
+ if matches ! ( fk, intravisit:: FnKind :: Closure ) {
626
+ self . visit_body ( self . tcx . hir ( ) . body ( b) )
627
+ }
628
+ }
629
+ }
630
+
631
+ fn report_unused_unsafe ( tcx : TyCtxt < ' _ > , id : hir:: HirId , kind : UnusedUnsafe ) {
632
+ let span = tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ;
633
+ tcx. struct_span_lint_hir ( UNUSED_UNSAFE , id, span, |lint| {
634
+ let msg = "unnecessary `unsafe` block" ;
635
+ let mut db = lint. build ( msg) ;
636
+ db. span_label ( span, msg) ;
637
+ match kind {
638
+ UnusedUnsafe :: Unused => { }
639
+ UnusedUnsafe :: InUnsafeBlock ( id) => {
640
+ db. span_label (
641
+ tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ,
642
+ format ! ( "because it's nested under this `unsafe` block" ) ,
643
+ ) ;
644
+ }
645
+ UnusedUnsafe :: InUnsafeFn ( id, usage_lint_root) => {
646
+ db. span_label (
647
+ tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ,
648
+ format ! ( "because it's nested under this `unsafe` fn" ) ,
649
+ )
650
+ . note (
651
+ "this `unsafe` block does contain unsafe operations, \
652
+ but those are already allowed in an `unsafe fn`",
653
+ ) ;
654
+ let ( level, source) =
655
+ tcx. lint_level_at_node ( UNSAFE_OP_IN_UNSAFE_FN , usage_lint_root) ;
656
+ assert_eq ! ( level, Level :: Allow ) ;
657
+ lint:: explain_lint_level_source (
658
+ tcx. sess ,
659
+ UNSAFE_OP_IN_UNSAFE_FN ,
660
+ Level :: Allow ,
661
+ source,
662
+ & mut db,
663
+ ) ;
664
+ }
665
+ }
666
+
667
+ db. emit ( ) ;
668
+ } ) ;
669
+ }
670
+
599
671
pub fn check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def : ty:: WithOptConstParam < LocalDefId > ) {
600
672
// THIR unsafeck is gated under `-Z thir-unsafeck`
601
673
if !tcx. sess . opts . debugging_opts . thir_unsafeck {
@@ -633,14 +705,20 @@ pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalD
633
705
thir,
634
706
safety_context,
635
707
hir_context : hir_id,
636
- body_unsafety,
637
708
body_target_features,
638
709
assignment_info : None ,
639
710
in_union_destructure : false ,
640
711
param_env : tcx. param_env ( def. did ) ,
641
712
inside_adt : false ,
713
+ used_unsafe_blocks : & mut Default :: default ( ) ,
642
714
} ;
643
715
visitor. visit_expr ( & thir[ expr] ) ;
716
+ let mut visitor2 = UnusedUnsafeVisitor {
717
+ tcx,
718
+ used_unsafe_blocks : visitor. used_unsafe_blocks ,
719
+ context : if body_unsafety. is_unsafe ( ) { Context :: UnsafeFn ( hir_id) } else { Context :: Safe } ,
720
+ } ;
721
+ intravisit:: Visitor :: visit_body ( & mut visitor2, tcx. hir ( ) . body ( tcx. hir ( ) . body_owned_by ( hir_id) ) ) ;
644
722
}
645
723
646
724
crate fn thir_check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def_id : LocalDefId ) {
0 commit comments