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
15
17
use std:: borrow:: Cow ;
18
+ use std:: collections:: hash_map;
16
19
use std:: ops:: Bound ;
17
20
18
21
struct UnsafetyVisitor < ' a , ' tcx > {
@@ -24,7 +27,6 @@ struct UnsafetyVisitor<'a, 'tcx> {
24
27
/// The current "safety context". This notably tracks whether we are in an
25
28
/// `unsafe` block, and whether it has been used.
26
29
safety_context : SafetyContext ,
27
- body_unsafety : BodyUnsafety ,
28
30
/// The `#[target_feature]` attributes of the body. Used for checking
29
31
/// calls to functions with `#[target_feature]` (RFC 2396).
30
32
body_target_features : & ' tcx [ Symbol ] ,
@@ -34,51 +36,41 @@ struct UnsafetyVisitor<'a, 'tcx> {
34
36
in_union_destructure : bool ,
35
37
param_env : ParamEnv < ' tcx > ,
36
38
inside_adt : bool ,
39
+ used_unsafe_blocks : & ' a mut FxHashMap < hir:: HirId , UsedUnsafeBlockData > ,
37
40
}
38
41
39
42
impl < ' tcx > UnsafetyVisitor < ' _ , ' tcx > {
40
43
fn in_safety_context ( & mut self , safety_context : SafetyContext , f : impl FnOnce ( & mut Self ) ) {
41
- if let (
42
- SafetyContext :: UnsafeBlock { span : enclosing_span, .. } ,
43
- SafetyContext :: UnsafeBlock { span : block_span, hir_id, .. } ,
44
- ) = ( self . safety_context , safety_context)
45
- {
46
- self . warn_unused_unsafe (
47
- hir_id,
48
- block_span,
49
- Some ( ( self . tcx . sess . source_map ( ) . guess_head_span ( enclosing_span) , "block" ) ) ,
50
- ) ;
51
- f ( self ) ;
52
- } else {
53
- let prev_context = self . safety_context ;
54
- self . safety_context = safety_context;
44
+ let prev_context = self . safety_context ;
45
+ self . safety_context = safety_context;
55
46
56
- f ( self ) ;
47
+ f ( self ) ;
57
48
58
- if let SafetyContext :: UnsafeBlock { used : false , span, hir_id } = self . safety_context {
59
- self . warn_unused_unsafe (
60
- hir_id,
61
- span,
62
- if self . unsafe_op_in_unsafe_fn_allowed ( ) {
63
- self . body_unsafety . unsafe_fn_sig_span ( ) . map ( |span| ( span, "fn" ) )
64
- } else {
65
- None
66
- } ,
67
- ) ;
68
- }
69
- self . safety_context = prev_context;
70
- }
49
+ self . safety_context = prev_context;
71
50
}
72
51
73
52
fn requires_unsafe ( & mut self , span : Span , kind : UnsafeOpKind ) {
53
+ use UsedUnsafeBlockData :: { AllAllowedInUnsafeFn , SomeDisallowedInUnsafeFn } ;
54
+
74
55
let unsafe_op_in_unsafe_fn_allowed = self . unsafe_op_in_unsafe_fn_allowed ( ) ;
75
56
match self . safety_context {
76
57
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
- }
58
+ SafetyContext :: UnsafeBlock ( hir_id) => {
59
+ let new_usage = if unsafe_op_in_unsafe_fn_allowed {
60
+ AllAllowedInUnsafeFn ( self . hir_context )
61
+ } else {
62
+ SomeDisallowedInUnsafeFn
63
+ } ;
64
+ match self . used_unsafe_blocks . entry ( hir_id) {
65
+ hash_map:: Entry :: Occupied ( mut entry) => {
66
+ if new_usage == SomeDisallowedInUnsafeFn {
67
+ * entry. get_mut ( ) = SomeDisallowedInUnsafeFn ;
68
+ }
69
+ }
70
+ hash_map:: Entry :: Vacant ( entry) => {
71
+ entry. insert ( new_usage) ;
72
+ }
73
+ } ;
82
74
}
83
75
SafetyContext :: UnsafeFn if unsafe_op_in_unsafe_fn_allowed => { }
84
76
SafetyContext :: UnsafeFn => {
@@ -117,24 +109,6 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
117
109
}
118
110
}
119
111
120
- fn warn_unused_unsafe (
121
- & self ,
122
- hir_id : hir:: HirId ,
123
- block_span : Span ,
124
- enclosing_unsafe : Option < ( Span , & ' static str ) > ,
125
- ) {
126
- let block_span = self . tcx . sess . source_map ( ) . guess_head_span ( block_span) ;
127
- self . tcx . struct_span_lint_hir ( UNUSED_UNSAFE , hir_id, block_span, |lint| {
128
- let msg = "unnecessary `unsafe` block" ;
129
- let mut db = lint. build ( msg) ;
130
- db. span_label ( block_span, msg) ;
131
- if let Some ( ( span, kind) ) = enclosing_unsafe {
132
- db. span_label ( span, format ! ( "because it's nested under this `unsafe` {}" , kind) ) ;
133
- }
134
- db. emit ( ) ;
135
- } ) ;
136
- }
137
-
138
112
/// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
139
113
fn unsafe_op_in_unsafe_fn_allowed ( & self ) -> bool {
140
114
self . tcx . lint_level_at_node ( UNSAFE_OP_IN_UNSAFE_FN , self . hir_context ) . 0 == Level :: Allow
@@ -200,10 +174,9 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
200
174
} ) ;
201
175
}
202
176
BlockSafety :: ExplicitUnsafe ( hir_id) => {
203
- self . in_safety_context (
204
- SafetyContext :: UnsafeBlock { span : block. span , hir_id, used : false } ,
205
- |this| visit:: walk_block ( this, block) ,
206
- ) ;
177
+ self . in_safety_context ( SafetyContext :: UnsafeBlock ( hir_id) , |this| {
178
+ visit:: walk_block ( this, block)
179
+ } ) ;
207
180
}
208
181
BlockSafety :: Safe => {
209
182
visit:: walk_block ( self , block) ;
@@ -421,8 +394,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
421
394
} ) ;
422
395
let closure_thir = & closure_thir. borrow ( ) ;
423
396
let hir_context = self . tcx . hir ( ) . local_def_id_to_hir_id ( closure_id) ;
424
- let mut closure_visitor =
425
- UnsafetyVisitor { thir : closure_thir, hir_context, ..* self } ;
397
+ let mut closure_visitor = UnsafetyVisitor {
398
+ thir : closure_thir,
399
+ hir_context,
400
+ used_unsafe_blocks : self . used_unsafe_blocks ,
401
+ ..* self
402
+ } ;
426
403
closure_visitor. visit_expr ( & closure_thir[ expr] ) ;
427
404
// Unsafe blocks can be used in closures, make sure to take it into account
428
405
self . safety_context = closure_visitor. safety_context ;
@@ -495,7 +472,7 @@ enum SafetyContext {
495
472
Safe ,
496
473
BuiltinUnsafeBlock ,
497
474
UnsafeFn ,
498
- UnsafeBlock { span : Span , hir_id : hir:: HirId , used : bool } ,
475
+ UnsafeBlock ( hir:: HirId ) ,
499
476
}
500
477
501
478
#[ derive( Clone , Copy ) ]
@@ -512,14 +489,6 @@ impl BodyUnsafety {
512
489
fn is_unsafe ( & self ) -> bool {
513
490
matches ! ( self , BodyUnsafety :: Unsafe ( _) )
514
491
}
515
-
516
- /// If the body is unsafe, returns the `Span` of its signature.
517
- fn unsafe_fn_sig_span ( self ) -> Option < Span > {
518
- match self {
519
- BodyUnsafety :: Unsafe ( span) => Some ( span) ,
520
- BodyUnsafety :: Safe => None ,
521
- }
522
- }
523
492
}
524
493
525
494
#[ derive( Clone , Copy , PartialEq ) ]
@@ -616,6 +585,106 @@ impl UnsafeOpKind {
616
585
}
617
586
}
618
587
588
+ /// Context information for [`UnusedUnsafeVisitor`] traversal,
589
+ /// saves (innermost) relevant context
590
+ #[ derive( Copy , Clone , Debug ) ]
591
+ enum Context {
592
+ Safe ,
593
+ /// in an `unsafe fn`
594
+ UnsafeFn ( hir:: HirId ) ,
595
+ /// in a *used* `unsafe` block
596
+ /// (i.e. a block without unused-unsafe warning)
597
+ UnsafeBlock ( hir:: HirId ) ,
598
+ }
599
+
600
+ struct UnusedUnsafeVisitor < ' a , ' tcx > {
601
+ tcx : TyCtxt < ' tcx > ,
602
+ used_unsafe_blocks : & ' a FxHashMap < hir:: HirId , UsedUnsafeBlockData > ,
603
+ context : Context ,
604
+ }
605
+
606
+ impl < ' tcx > intravisit:: Visitor < ' tcx > for UnusedUnsafeVisitor < ' _ , ' tcx > {
607
+ fn visit_block ( & mut self , block : & ' tcx hir:: Block < ' tcx > ) {
608
+ use UsedUnsafeBlockData :: { AllAllowedInUnsafeFn , SomeDisallowedInUnsafeFn } ;
609
+
610
+ if let hir:: BlockCheckMode :: UnsafeBlock ( hir:: UnsafeSource :: UserProvided ) = block. rules {
611
+ let used = match self . tcx . lint_level_at_node ( UNUSED_UNSAFE , block. hir_id ) {
612
+ ( Level :: Allow , _) => Some ( SomeDisallowedInUnsafeFn ) ,
613
+ _ => self . used_unsafe_blocks . get ( & block. hir_id ) . copied ( ) ,
614
+ } ;
615
+ let unused_unsafe = match ( self . context , used) {
616
+ ( _, None ) => UnusedUnsafe :: Unused ,
617
+ ( Context :: Safe , Some ( _) )
618
+ | ( Context :: UnsafeFn ( _) , Some ( SomeDisallowedInUnsafeFn ) ) => {
619
+ let previous_context = self . context ;
620
+ self . context = Context :: UnsafeBlock ( block. hir_id ) ;
621
+ intravisit:: walk_block ( self , block) ;
622
+ self . context = previous_context;
623
+ return ;
624
+ }
625
+ ( Context :: UnsafeFn ( hir_id) , Some ( AllAllowedInUnsafeFn ( lint_root) ) ) => {
626
+ UnusedUnsafe :: InUnsafeFn ( hir_id, lint_root)
627
+ }
628
+ ( Context :: UnsafeBlock ( hir_id) , Some ( _) ) => UnusedUnsafe :: InUnsafeBlock ( hir_id) ,
629
+ } ;
630
+ report_unused_unsafe ( self . tcx , block. hir_id , unused_unsafe) ;
631
+ }
632
+ intravisit:: walk_block ( self , block) ;
633
+ }
634
+
635
+ fn visit_fn (
636
+ & mut self ,
637
+ fk : intravisit:: FnKind < ' tcx > ,
638
+ _fd : & ' tcx hir:: FnDecl < ' tcx > ,
639
+ b : hir:: BodyId ,
640
+ _s : rustc_span:: Span ,
641
+ _id : hir:: HirId ,
642
+ ) {
643
+ if matches ! ( fk, intravisit:: FnKind :: Closure ) {
644
+ self . visit_body ( self . tcx . hir ( ) . body ( b) )
645
+ }
646
+ }
647
+ }
648
+
649
+ fn report_unused_unsafe ( tcx : TyCtxt < ' _ > , id : hir:: HirId , kind : UnusedUnsafe ) {
650
+ let span = tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ;
651
+ tcx. struct_span_lint_hir ( UNUSED_UNSAFE , id, span, |lint| {
652
+ let msg = "unnecessary `unsafe` block" ;
653
+ let mut db = lint. build ( msg) ;
654
+ db. span_label ( span, msg) ;
655
+ match kind {
656
+ UnusedUnsafe :: Unused => { }
657
+ UnusedUnsafe :: InUnsafeBlock ( id) => {
658
+ db. span_label (
659
+ tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ,
660
+ format ! ( "because it's nested under this `unsafe` block" ) ,
661
+ ) ;
662
+ }
663
+ UnusedUnsafe :: InUnsafeFn ( id, usage_lint_root) => {
664
+ db. span_label (
665
+ tcx. sess . source_map ( ) . guess_head_span ( tcx. hir ( ) . span ( id) ) ,
666
+ format ! ( "because it's nested under this `unsafe` fn" ) ,
667
+ )
668
+ . note (
669
+ "this `unsafe` block does contain unsafe operations, \
670
+ but those are already allowed in an `unsafe fn`",
671
+ ) ;
672
+ let ( level, source) =
673
+ tcx. lint_level_at_node ( UNSAFE_OP_IN_UNSAFE_FN , usage_lint_root) ;
674
+ assert_eq ! ( level, Level :: Allow ) ;
675
+ lint:: explain_lint_level_source (
676
+ UNSAFE_OP_IN_UNSAFE_FN ,
677
+ Level :: Allow ,
678
+ source,
679
+ & mut db,
680
+ ) ;
681
+ }
682
+ }
683
+
684
+ db. emit ( ) ;
685
+ } ) ;
686
+ }
687
+
619
688
pub fn check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def : ty:: WithOptConstParam < LocalDefId > ) {
620
689
// THIR unsafeck is gated under `-Z thir-unsafeck`
621
690
if !tcx. sess . opts . unstable_opts . thir_unsafeck {
@@ -655,14 +724,20 @@ pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalD
655
724
thir,
656
725
safety_context,
657
726
hir_context : hir_id,
658
- body_unsafety,
659
727
body_target_features,
660
728
assignment_info : None ,
661
729
in_union_destructure : false ,
662
730
param_env : tcx. param_env ( def. did ) ,
663
731
inside_adt : false ,
732
+ used_unsafe_blocks : & mut Default :: default ( ) ,
664
733
} ;
665
734
visitor. visit_expr ( & thir[ expr] ) ;
735
+ let mut visitor2 = UnusedUnsafeVisitor {
736
+ tcx,
737
+ used_unsafe_blocks : visitor. used_unsafe_blocks ,
738
+ context : if body_unsafety. is_unsafe ( ) { Context :: UnsafeFn ( hir_id) } else { Context :: Safe } ,
739
+ } ;
740
+ intravisit:: Visitor :: visit_body ( & mut visitor2, tcx. hir ( ) . body ( tcx. hir ( ) . body_owned_by ( hir_id) ) ) ;
666
741
}
667
742
668
743
pub ( crate ) fn thir_check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def_id : LocalDefId ) {
0 commit comments