Skip to content

Commit dc1056a

Browse files
committed
Port improved unused_unsafe check to -Zthir_unsafeck
1 parent 3810f72 commit dc1056a

File tree

4 files changed

+1412
-100
lines changed

4 files changed

+1412
-100
lines changed

compiler/rustc_mir_build/src/check_unsafety.rs

+148-70
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
use crate::build::ExprCategory;
2+
use hir::intravisit;
3+
use rustc_data_structures::fx::FxHashMap;
24
use rustc_middle::thir::visit::{self, Visitor};
35

46
use rustc_errors::struct_span_err;
57
use rustc_hir as hir;
6-
use rustc_middle::mir::BorrowKind;
7-
use rustc_middle::thir::*;
8+
use rustc_middle::mir::{BorrowKind, UnusedUnsafe, UsedUnsafeBlockData};
89
use rustc_middle::ty::{self, ParamEnv, Ty, TyCtxt};
10+
use rustc_middle::{lint, thir::*};
911
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
1012
use rustc_session::lint::Level;
1113
use rustc_span::def_id::{DefId, LocalDefId};
1214
use rustc_span::symbol::Symbol;
1315
use rustc_span::Span;
1416

17+
use std::cmp;
18+
use std::collections::hash_map;
1519
use std::ops::Bound;
1620

1721
struct UnsafetyVisitor<'a, 'tcx> {
@@ -23,7 +27,6 @@ struct UnsafetyVisitor<'a, 'tcx> {
2327
/// The current "safety context". This notably tracks whether we are in an
2428
/// `unsafe` block, and whether it has been used.
2529
safety_context: SafetyContext,
26-
body_unsafety: BodyUnsafety,
2730
/// The `#[target_feature]` attributes of the body. Used for checking
2831
/// calls to functions with `#[target_feature]` (RFC 2396).
2932
body_target_features: &'tcx Vec<Symbol>,
@@ -33,52 +36,39 @@ struct UnsafetyVisitor<'a, 'tcx> {
3336
in_union_destructure: bool,
3437
param_env: ParamEnv<'tcx>,
3538
inside_adt: bool,
39+
used_unsafe_blocks: &'a mut FxHashMap<hir::HirId, UsedUnsafeBlockData>,
3640
}
3741

3842
impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
3943
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;
5446

55-
f(self);
47+
f(self);
5648

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;
7050
}
7151

7252
fn requires_unsafe(&mut self, span: Span, kind: UnsafeOpKind) {
7353
let (description, note) = kind.description_and_note();
7454
let unsafe_op_in_unsafe_fn_allowed = self.unsafe_op_in_unsafe_fn_allowed();
7555
match self.safety_context {
7656
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+
};
8272
}
8373
SafetyContext::UnsafeFn if unsafe_op_in_unsafe_fn_allowed => {}
8474
SafetyContext::UnsafeFn => {
@@ -115,24 +105,6 @@ impl<'tcx> UnsafetyVisitor<'_, 'tcx> {
115105
}
116106
}
117107

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-
136108
/// Whether the `unsafe_op_in_unsafe_fn` lint is `allow`ed at the current HIR node.
137109
fn unsafe_op_in_unsafe_fn_allowed(&self) -> bool {
138110
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> {
198170
});
199171
}
200172
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+
});
205176
}
206177
BlockSafety::Safe => {
207178
visit::walk_block(self, block);
@@ -408,8 +379,12 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
408379
let (closure_thir, expr) = self.tcx.thir_body(closure_def);
409380
let closure_thir = &closure_thir.borrow();
410381
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+
};
413388
closure_visitor.visit_expr(&closure_thir[expr]);
414389
// Unsafe blocks can be used in closures, make sure to take it into account
415390
self.safety_context = closure_visitor.safety_context;
@@ -493,7 +468,7 @@ enum SafetyContext {
493468
Safe,
494469
BuiltinUnsafeBlock,
495470
UnsafeFn,
496-
UnsafeBlock { span: Span, hir_id: hir::HirId, used: bool },
471+
UnsafeBlock(hir::HirId),
497472
}
498473

499474
#[derive(Clone, Copy)]
@@ -510,14 +485,6 @@ impl BodyUnsafety {
510485
fn is_unsafe(&self) -> bool {
511486
matches!(self, BodyUnsafety::Unsafe(_))
512487
}
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-
}
521488
}
522489

523490
#[derive(Clone, Copy, PartialEq)]
@@ -596,6 +563,111 @@ impl UnsafeOpKind {
596563
}
597564
}
598565

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+
599671
pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalDefId>) {
600672
// THIR unsafeck is gated under `-Z thir-unsafeck`
601673
if !tcx.sess.opts.debugging_opts.thir_unsafeck {
@@ -633,14 +705,20 @@ pub fn check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def: ty::WithOptConstParam<LocalD
633705
thir,
634706
safety_context,
635707
hir_context: hir_id,
636-
body_unsafety,
637708
body_target_features,
638709
assignment_info: None,
639710
in_union_destructure: false,
640711
param_env: tcx.param_env(def.did),
641712
inside_adt: false,
713+
used_unsafe_blocks: &mut Default::default(),
642714
};
643715
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)));
644722
}
645723

646724
crate fn thir_check_unsafety<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) {

src/test/ui/span/lint-unused-unsafe.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33

44
// edition:2018
5-
// revisions: mir
5+
// // revisions: mir
66

7-
// // revisions: mir thir
8-
// // [thir]compile-flags: -Zthir-unsafeck
7+
// revisions: mir thir
8+
// [thir]compile-flags: -Zthir-unsafeck
99

1010
#![allow(dead_code)]
1111
#![deny(unused_unsafe)]

0 commit comments

Comments
 (0)