11use crate :: build:: ExprCategory ;
2+ use hir:: intravisit;
3+ use rustc_data_structures:: fx:: FxHashMap ;
24use rustc_middle:: thir:: visit:: { self , Visitor } ;
35
46use rustc_errors:: struct_span_err;
57use rustc_hir as hir;
6- use rustc_middle:: mir:: BorrowKind ;
7- use rustc_middle:: thir:: * ;
8+ use rustc_middle:: mir:: { BorrowKind , UnusedUnsafe , UsedUnsafeBlockData } ;
89use rustc_middle:: ty:: { self , ParamEnv , Ty , TyCtxt } ;
10+ use rustc_middle:: { lint, thir:: * } ;
911use rustc_session:: lint:: builtin:: { UNSAFE_OP_IN_UNSAFE_FN , UNUSED_UNSAFE } ;
1012use rustc_session:: lint:: Level ;
1113use rustc_span:: def_id:: { DefId , LocalDefId } ;
1214use rustc_span:: symbol:: Symbol ;
1315use rustc_span:: Span ;
1416
1517use std:: borrow:: Cow ;
18+ use std:: collections:: hash_map;
1619use std:: ops:: Bound ;
1720
1821struct UnsafetyVisitor < ' a , ' tcx > {
@@ -24,7 +27,6 @@ struct UnsafetyVisitor<'a, 'tcx> {
2427 /// The current "safety context". This notably tracks whether we are in an
2528 /// `unsafe` block, and whether it has been used.
2629 safety_context : SafetyContext ,
27- body_unsafety : BodyUnsafety ,
2830 /// The `#[target_feature]` attributes of the body. Used for checking
2931 /// calls to functions with `#[target_feature]` (RFC 2396).
3032 body_target_features : & ' tcx [ Symbol ] ,
@@ -34,51 +36,41 @@ struct UnsafetyVisitor<'a, 'tcx> {
3436 in_union_destructure : bool ,
3537 param_env : ParamEnv < ' tcx > ,
3638 inside_adt : bool ,
39+ used_unsafe_blocks : & ' a mut FxHashMap < hir:: HirId , UsedUnsafeBlockData > ,
3740}
3841
3942impl < ' tcx > UnsafetyVisitor < ' _ , ' tcx > {
4043 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;
5546
56- f ( self ) ;
47+ f ( self ) ;
5748
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;
7150 }
7251
7352 fn requires_unsafe ( & mut self , span : Span , kind : UnsafeOpKind ) {
53+ use UsedUnsafeBlockData :: { AllAllowedInUnsafeFn , SomeDisallowedInUnsafeFn } ;
54+
7455 let unsafe_op_in_unsafe_fn_allowed = self . unsafe_op_in_unsafe_fn_allowed ( ) ;
7556 match self . safety_context {
7657 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+ } ;
8274 }
8375 SafetyContext :: UnsafeFn if unsafe_op_in_unsafe_fn_allowed => { }
8476 SafetyContext :: UnsafeFn => {
@@ -117,24 +109,6 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
117109 }
118110 }
119111
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-
138112 /// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
139113 fn unsafe_op_in_unsafe_fn_allowed ( & self ) -> bool {
140114 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> {
200174 } ) ;
201175 }
202176 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+ } ) ;
207180 }
208181 BlockSafety :: Safe => {
209182 visit:: walk_block ( self , block) ;
@@ -421,8 +394,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
421394 } ) ;
422395 let closure_thir = & closure_thir. borrow ( ) ;
423396 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+ } ;
426403 closure_visitor. visit_expr ( & closure_thir[ expr] ) ;
427404 // Unsafe blocks can be used in closures, make sure to take it into account
428405 self . safety_context = closure_visitor. safety_context ;
@@ -495,7 +472,7 @@ enum SafetyContext {
495472 Safe ,
496473 BuiltinUnsafeBlock ,
497474 UnsafeFn ,
498- UnsafeBlock { span : Span , hir_id : hir:: HirId , used : bool } ,
475+ UnsafeBlock ( hir:: HirId ) ,
499476}
500477
501478#[ derive( Clone , Copy ) ]
@@ -512,14 +489,6 @@ impl BodyUnsafety {
512489 fn is_unsafe ( & self ) -> bool {
513490 matches ! ( self , BodyUnsafety :: Unsafe ( _) )
514491 }
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- }
523492}
524493
525494#[ derive( Clone , Copy , PartialEq ) ]
@@ -616,6 +585,106 @@ impl UnsafeOpKind {
616585 }
617586}
618587
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+
619688pub fn check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def : ty:: WithOptConstParam < LocalDefId > ) {
620689 // THIR unsafeck is gated under `-Z thir-unsafeck`
621690 if !tcx. sess . opts . unstable_opts . thir_unsafeck {
@@ -655,14 +724,20 @@ pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalD
655724 thir,
656725 safety_context,
657726 hir_context : hir_id,
658- body_unsafety,
659727 body_target_features,
660728 assignment_info : None ,
661729 in_union_destructure : false ,
662730 param_env : tcx. param_env ( def. did ) ,
663731 inside_adt : false ,
732+ used_unsafe_blocks : & mut Default :: default ( ) ,
664733 } ;
665734 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) ) ) ;
666741}
667742
668743pub ( crate ) fn thir_check_unsafety < ' tcx > ( tcx : TyCtxt < ' tcx > , def_id : LocalDefId ) {
0 commit comments