Auto merge of #93678 - steffahn:better_unsafe_diagnostics, r=nagisa
Improve `unused_unsafe` lint I’m going to add some motivation and explanation below, particularly pointing the changes in behavior from this PR. _Edit:_ Looking for existing issues, looks like this PR fixes #88260. _Edit2:_ Now also contains code that closes #90776.
This commit is contained in:
commit
45e2c2881d
@ -599,13 +599,11 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
|
||||
if self.conv == Conv::CCmseNonSecureCall {
|
||||
// This will probably get ignored on all targets but those supporting the TrustZone-M
|
||||
// extension (thumbv8m targets).
|
||||
unsafe {
|
||||
llvm::AddCallSiteAttrString(
|
||||
callsite,
|
||||
llvm::AttributePlace::Function,
|
||||
cstr::cstr!("cmse_nonsecure_call"),
|
||||
);
|
||||
}
|
||||
llvm::AddCallSiteAttrString(
|
||||
callsite,
|
||||
llvm::AttributePlace::Function,
|
||||
cstr::cstr!("cmse_nonsecure_call"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,10 @@ use rustc_hir::intravisit::nested_filter::NestedFilter;
|
||||
/// Do not visit nested item-like things, but visit nested things
|
||||
/// that are inside of an item-like.
|
||||
///
|
||||
/// Notably, possible occurrences of bodies in non-item-like things
|
||||
/// include: closures/generators, inline `const {}` blocks, and
|
||||
/// constant arguments of types, e.g. in `let _: [(); /* HERE */];`.
|
||||
///
|
||||
/// **This is the most common choice.** A very common pattern is
|
||||
/// to use `visit_all_item_likes()` as an outer loop,
|
||||
/// and to have the visitor that visits the contents of each item
|
||||
|
@ -202,6 +202,77 @@ impl<'a> LintDiagnosticBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn explain_lint_level_source<'s>(
|
||||
sess: &'s Session,
|
||||
lint: &'static Lint,
|
||||
level: Level,
|
||||
src: LintLevelSource,
|
||||
err: &mut DiagnosticBuilder<'s>,
|
||||
) {
|
||||
let name = lint.name_lower();
|
||||
match src {
|
||||
LintLevelSource::Default => {
|
||||
sess.diag_note_once(
|
||||
err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!("`#[{}({})]` on by default", level.as_str(), name),
|
||||
);
|
||||
}
|
||||
LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
|
||||
let flag = match orig_level {
|
||||
Level::Warn => "-W",
|
||||
Level::Deny => "-D",
|
||||
Level::Forbid => "-F",
|
||||
Level::Allow => "-A",
|
||||
Level::ForceWarn => "--force-warn",
|
||||
};
|
||||
let hyphen_case_lint_name = name.replace('_', "-");
|
||||
if lint_flag_val.as_str() == name {
|
||||
sess.diag_note_once(
|
||||
err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"requested on the command line with `{} {}`",
|
||||
flag, hyphen_case_lint_name
|
||||
),
|
||||
);
|
||||
} else {
|
||||
let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
|
||||
sess.diag_note_once(
|
||||
err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"`{} {}` implied by `{} {}`",
|
||||
flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
LintLevelSource::Node(lint_attr_name, src, reason) => {
|
||||
if let Some(rationale) = reason {
|
||||
err.note(rationale.as_str());
|
||||
}
|
||||
sess.diag_span_note_once(
|
||||
err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
src,
|
||||
"the lint level is defined here",
|
||||
);
|
||||
if lint_attr_name.as_str() != name {
|
||||
let level_str = level.as_str();
|
||||
sess.diag_note_once(
|
||||
err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"`#[{}({})]` implied by `#[{}({})]`",
|
||||
level_str, name, level_str, lint_attr_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn struct_lint_level<'s, 'd>(
|
||||
sess: &'s Session,
|
||||
lint: &'static Lint,
|
||||
@ -277,69 +348,9 @@ pub fn struct_lint_level<'s, 'd>(
|
||||
}
|
||||
}
|
||||
|
||||
let name = lint.name_lower();
|
||||
match src {
|
||||
LintLevelSource::Default => {
|
||||
sess.diag_note_once(
|
||||
&mut err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!("`#[{}({})]` on by default", level.as_str(), name),
|
||||
);
|
||||
}
|
||||
LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
|
||||
let flag = match orig_level {
|
||||
Level::Warn => "-W",
|
||||
Level::Deny => "-D",
|
||||
Level::Forbid => "-F",
|
||||
Level::Allow => "-A",
|
||||
Level::ForceWarn => "--force-warn",
|
||||
};
|
||||
let hyphen_case_lint_name = name.replace('_', "-");
|
||||
if lint_flag_val.as_str() == name {
|
||||
sess.diag_note_once(
|
||||
&mut err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"requested on the command line with `{} {}`",
|
||||
flag, hyphen_case_lint_name
|
||||
),
|
||||
);
|
||||
} else {
|
||||
let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
|
||||
sess.diag_note_once(
|
||||
&mut err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"`{} {}` implied by `{} {}`",
|
||||
flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
LintLevelSource::Node(lint_attr_name, src, reason) => {
|
||||
if let Some(rationale) = reason {
|
||||
err.note(rationale.as_str());
|
||||
}
|
||||
sess.diag_span_note_once(
|
||||
&mut err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
src,
|
||||
"the lint level is defined here",
|
||||
);
|
||||
if lint_attr_name.as_str() != name {
|
||||
let level_str = level.as_str();
|
||||
sess.diag_note_once(
|
||||
&mut err,
|
||||
DiagnosticMessageId::from(lint),
|
||||
&format!(
|
||||
"`#[{}({})]` implied by `#[{}({})]`",
|
||||
level_str, name, level_str, lint_attr_name
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
explain_lint_level_source(sess, lint, level, src, &mut err);
|
||||
|
||||
let name = lint.name_lower();
|
||||
let is_force_warn = matches!(level, Level::ForceWarn);
|
||||
err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn });
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
use crate::mir::{Body, Promoted};
|
||||
use crate::ty::{self, Ty, TyCtxt};
|
||||
use rustc_data_structures::sync::Lrc;
|
||||
use rustc_data_structures::stable_map::FxHashMap;
|
||||
use rustc_data_structures::vec_map::VecMap;
|
||||
use rustc_errors::ErrorReported;
|
||||
use rustc_hir as hir;
|
||||
@ -114,13 +114,44 @@ pub struct UnsafetyViolation {
|
||||
pub details: UnsafetyViolationDetails,
|
||||
}
|
||||
|
||||
#[derive(Clone, TyEncodable, TyDecodable, HashStable, Debug)]
|
||||
#[derive(Copy, Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
|
||||
pub enum UnusedUnsafe {
|
||||
/// `unsafe` block contains no unsafe operations
|
||||
/// > ``unnecessary `unsafe` block``
|
||||
Unused,
|
||||
/// `unsafe` block nested under another (used) `unsafe` block
|
||||
/// > ``… because it's nested under this `unsafe` block``
|
||||
InUnsafeBlock(hir::HirId),
|
||||
/// `unsafe` block nested under `unsafe fn`
|
||||
/// > ``… because it's nested under this `unsafe fn` ``
|
||||
///
|
||||
/// the second HirId here indicates the first usage of the `unsafe` block,
|
||||
/// which allows retrival of the LintLevelSource for why that operation would
|
||||
/// have been permitted without the block
|
||||
InUnsafeFn(hir::HirId, hir::HirId),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
|
||||
pub enum UsedUnsafeBlockData {
|
||||
SomeDisallowedInUnsafeFn,
|
||||
// the HirId here indicates the first usage of the `unsafe` block
|
||||
// (i.e. the one that's first encountered in the MIR traversal of the unsafety check)
|
||||
AllAllowedInUnsafeFn(hir::HirId),
|
||||
}
|
||||
|
||||
#[derive(TyEncodable, TyDecodable, HashStable, Debug)]
|
||||
pub struct UnsafetyCheckResult {
|
||||
/// Violations that are propagated *upwards* from this function.
|
||||
pub violations: Lrc<[UnsafetyViolation]>,
|
||||
/// `unsafe` blocks in this function, along with whether they are used. This is
|
||||
/// used for the "unused_unsafe" lint.
|
||||
pub unsafe_blocks: Lrc<[(hir::HirId, bool)]>,
|
||||
pub violations: Vec<UnsafetyViolation>,
|
||||
|
||||
/// Used `unsafe` blocks in this function. This is used for the "unused_unsafe" lint.
|
||||
///
|
||||
/// The keys are the used `unsafe` blocks, the UnusedUnsafeKind indicates whether
|
||||
/// or not any of the usages happen at a place that doesn't allow `unsafe_op_in_unsafe_fn`.
|
||||
pub used_unsafe_blocks: FxHashMap<hir::HirId, UsedUnsafeBlockData>,
|
||||
|
||||
/// This is `Some` iff the item is not a closure.
|
||||
pub unused_unsafes: Option<Vec<(hir::HirId, UnusedUnsafe)>>,
|
||||
}
|
||||
|
||||
rustc_index::newtype_index! {
|
||||
|
@ -3,8 +3,6 @@ use crate::build::ForGuard::OutsideGuard;
|
||||
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::thir::*;
|
||||
use rustc_session::lint::builtin::UNSAFE_OP_IN_UNSAFE_FN;
|
||||
use rustc_session::lint::Level;
|
||||
use rustc_span::Span;
|
||||
|
||||
impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
@ -209,28 +207,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||
block.unit()
|
||||
}
|
||||
|
||||
/// If we are changing the safety mode, create a new source scope
|
||||
/// If we are entering an unsafe block, create a new source scope
|
||||
fn update_source_scope_for_safety_mode(&mut self, span: Span, safety_mode: BlockSafety) {
|
||||
debug!("update_source_scope_for({:?}, {:?})", span, safety_mode);
|
||||
let new_unsafety = match safety_mode {
|
||||
BlockSafety::Safe => None,
|
||||
BlockSafety::BuiltinUnsafe => Some(Safety::BuiltinUnsafe),
|
||||
BlockSafety::Safe => return,
|
||||
BlockSafety::BuiltinUnsafe => Safety::BuiltinUnsafe,
|
||||
BlockSafety::ExplicitUnsafe(hir_id) => {
|
||||
match self.in_scope_unsafe {
|
||||
Safety::Safe => {}
|
||||
// no longer treat `unsafe fn`s as `unsafe` contexts (see RFC #2585)
|
||||
Safety::FnUnsafe
|
||||
if self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, hir_id).0
|
||||
!= Level::Allow => {}
|
||||
_ => return,
|
||||
}
|
||||
self.in_scope_unsafe = Safety::ExplicitUnsafe(hir_id);
|
||||
Some(Safety::ExplicitUnsafe(hir_id))
|
||||
Safety::ExplicitUnsafe(hir_id)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(unsafety) = new_unsafety {
|
||||
self.source_scope = self.new_source_scope(span, LintLevel::Inherited, Some(unsafety));
|
||||
}
|
||||
self.source_scope = self.new_source_scope(span, LintLevel::Inherited, Some(new_unsafety));
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
use rustc_data_structures::fx::FxHashSet;
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_errors::struct_span_err;
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def_id::{DefId, LocalDefId};
|
||||
use rustc_hir::hir_id::HirId;
|
||||
use rustc_hir::intravisit;
|
||||
use rustc_hir::Node;
|
||||
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
|
||||
use rustc_middle::mir::*;
|
||||
use rustc_middle::ty::query::Providers;
|
||||
use rustc_middle::ty::{self, TyCtxt};
|
||||
use rustc_middle::{lint, mir::*};
|
||||
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
|
||||
use rustc_session::lint::Level;
|
||||
|
||||
use std::collections::hash_map;
|
||||
use std::ops::Bound;
|
||||
|
||||
pub struct UnsafetyChecker<'a, 'tcx> {
|
||||
@ -21,9 +21,12 @@ pub struct UnsafetyChecker<'a, 'tcx> {
|
||||
source_info: SourceInfo,
|
||||
tcx: TyCtxt<'tcx>,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
/// Mark an `unsafe` block as used, so we don't lint it.
|
||||
used_unsafe: FxHashSet<hir::HirId>,
|
||||
inherited_blocks: Vec<(hir::HirId, bool)>,
|
||||
|
||||
/// Used `unsafe` blocks in this function. This is used for the "unused_unsafe" lint.
|
||||
///
|
||||
/// The keys are the used `unsafe` blocks, the UnusedUnsafeKind indicates whether
|
||||
/// or not any of the usages happen at a place that doesn't allow `unsafe_op_in_unsafe_fn`.
|
||||
used_unsafe_blocks: FxHashMap<HirId, UsedUnsafeBlockData>,
|
||||
}
|
||||
|
||||
impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
|
||||
@ -40,8 +43,7 @@ impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
|
||||
source_info: SourceInfo::outermost(body.span),
|
||||
tcx,
|
||||
param_env,
|
||||
used_unsafe: Default::default(),
|
||||
inherited_blocks: vec![],
|
||||
used_unsafe_blocks: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,9 +125,9 @@ impl<'tcx> Visitor<'tcx> for UnsafetyChecker<'_, 'tcx> {
|
||||
}
|
||||
}
|
||||
&AggregateKind::Closure(def_id, _) | &AggregateKind::Generator(def_id, _, _) => {
|
||||
let UnsafetyCheckResult { violations, unsafe_blocks } =
|
||||
let UnsafetyCheckResult { violations, used_unsafe_blocks, .. } =
|
||||
self.tcx.unsafety_check_result(def_id.expect_local());
|
||||
self.register_violations(&violations, &unsafe_blocks);
|
||||
self.register_violations(violations, used_unsafe_blocks);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
@ -251,61 +253,72 @@ impl<'tcx> UnsafetyChecker<'_, 'tcx> {
|
||||
.assert_crate_local()
|
||||
.lint_root;
|
||||
self.register_violations(
|
||||
&[UnsafetyViolation { source_info, lint_root, kind, details }],
|
||||
&[],
|
||||
[&UnsafetyViolation { source_info, lint_root, kind, details }],
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
fn register_violations(
|
||||
fn register_violations<'a>(
|
||||
&mut self,
|
||||
violations: &[UnsafetyViolation],
|
||||
unsafe_blocks: &[(hir::HirId, bool)],
|
||||
violations: impl IntoIterator<Item = &'a UnsafetyViolation>,
|
||||
new_used_unsafe_blocks: impl IntoIterator<Item = (&'a HirId, &'a UsedUnsafeBlockData)>,
|
||||
) {
|
||||
use UsedUnsafeBlockData::{AllAllowedInUnsafeFn, SomeDisallowedInUnsafeFn};
|
||||
|
||||
let update_entry = |this: &mut Self, hir_id, new_usage| {
|
||||
match this.used_unsafe_blocks.entry(hir_id) {
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
if new_usage == SomeDisallowedInUnsafeFn {
|
||||
*entry.get_mut() = SomeDisallowedInUnsafeFn;
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
entry.insert(new_usage);
|
||||
}
|
||||
};
|
||||
};
|
||||
let safety = self.body.source_scopes[self.source_info.scope]
|
||||
.local_data
|
||||
.as_ref()
|
||||
.assert_crate_local()
|
||||
.safety;
|
||||
let within_unsafe = match safety {
|
||||
match safety {
|
||||
// `unsafe` blocks are required in safe code
|
||||
Safety::Safe => {
|
||||
for violation in violations {
|
||||
match violation.kind {
|
||||
UnsafetyViolationKind::General => {}
|
||||
UnsafetyViolationKind::UnsafeFn => {
|
||||
bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context")
|
||||
}
|
||||
}
|
||||
if !self.violations.contains(violation) {
|
||||
self.violations.push(*violation)
|
||||
Safety::Safe => violations.into_iter().for_each(|&violation| {
|
||||
match violation.kind {
|
||||
UnsafetyViolationKind::General => {}
|
||||
UnsafetyViolationKind::UnsafeFn => {
|
||||
bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context")
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
if !self.violations.contains(&violation) {
|
||||
self.violations.push(violation)
|
||||
}
|
||||
}),
|
||||
// With the RFC 2585, no longer allow `unsafe` operations in `unsafe fn`s
|
||||
Safety::FnUnsafe => {
|
||||
for violation in violations {
|
||||
let mut violation = *violation;
|
||||
|
||||
violation.kind = UnsafetyViolationKind::UnsafeFn;
|
||||
if !self.violations.contains(&violation) {
|
||||
self.violations.push(violation)
|
||||
}
|
||||
Safety::FnUnsafe => violations.into_iter().for_each(|&(mut violation)| {
|
||||
violation.kind = UnsafetyViolationKind::UnsafeFn;
|
||||
if !self.violations.contains(&violation) {
|
||||
self.violations.push(violation)
|
||||
}
|
||||
false
|
||||
}
|
||||
Safety::BuiltinUnsafe => true,
|
||||
Safety::ExplicitUnsafe(hir_id) => {
|
||||
// mark unsafe block as used if there are any unsafe operations inside
|
||||
if !violations.is_empty() {
|
||||
self.used_unsafe.insert(hir_id);
|
||||
}
|
||||
true
|
||||
}
|
||||
}),
|
||||
Safety::BuiltinUnsafe => {}
|
||||
Safety::ExplicitUnsafe(hir_id) => violations.into_iter().for_each(|violation| {
|
||||
update_entry(
|
||||
self,
|
||||
hir_id,
|
||||
match self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, violation.lint_root).0
|
||||
{
|
||||
Level::Allow => AllAllowedInUnsafeFn(violation.lint_root),
|
||||
_ => SomeDisallowedInUnsafeFn,
|
||||
},
|
||||
)
|
||||
}),
|
||||
};
|
||||
self.inherited_blocks.extend(
|
||||
unsafe_blocks.iter().map(|&(hir_id, is_used)| (hir_id, is_used && !within_unsafe)),
|
||||
);
|
||||
|
||||
new_used_unsafe_blocks
|
||||
.into_iter()
|
||||
.for_each(|(&hir_id, &usage_data)| update_entry(self, hir_id, usage_data));
|
||||
}
|
||||
fn check_mut_borrowing_layout_constrained_field(
|
||||
&mut self,
|
||||
@ -387,17 +400,64 @@ pub(crate) fn provide(providers: &mut Providers) {
|
||||
};
|
||||
}
|
||||
|
||||
struct UnusedUnsafeVisitor<'a> {
|
||||
used_unsafe: &'a FxHashSet<hir::HirId>,
|
||||
unsafe_blocks: &'a mut Vec<(hir::HirId, bool)>,
|
||||
/// Context information for [`UnusedUnsafeVisitor`] traversal,
|
||||
/// saves (innermost) relevant context
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum Context {
|
||||
Safe,
|
||||
/// in an `unsafe fn`
|
||||
UnsafeFn(HirId),
|
||||
/// in a *used* `unsafe` block
|
||||
/// (i.e. a block without unused-unsafe warning)
|
||||
UnsafeBlock(HirId),
|
||||
}
|
||||
|
||||
impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_> {
|
||||
struct UnusedUnsafeVisitor<'a, 'tcx> {
|
||||
tcx: TyCtxt<'tcx>,
|
||||
used_unsafe_blocks: &'a FxHashMap<HirId, UsedUnsafeBlockData>,
|
||||
context: Context,
|
||||
unused_unsafes: &'a mut Vec<(HirId, UnusedUnsafe)>,
|
||||
}
|
||||
|
||||
impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> {
|
||||
fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
|
||||
intravisit::walk_block(self, block);
|
||||
use UsedUnsafeBlockData::{AllAllowedInUnsafeFn, SomeDisallowedInUnsafeFn};
|
||||
|
||||
if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules {
|
||||
self.unsafe_blocks.push((block.hir_id, self.used_unsafe.contains(&block.hir_id)));
|
||||
let used = match self.tcx.lint_level_at_node(UNUSED_UNSAFE, block.hir_id) {
|
||||
(Level::Allow, _) => Some(SomeDisallowedInUnsafeFn),
|
||||
_ => self.used_unsafe_blocks.get(&block.hir_id).copied(),
|
||||
};
|
||||
let unused_unsafe = match (self.context, used) {
|
||||
(_, None) => UnusedUnsafe::Unused,
|
||||
(Context::Safe, Some(_))
|
||||
| (Context::UnsafeFn(_), Some(SomeDisallowedInUnsafeFn)) => {
|
||||
let previous_context = self.context;
|
||||
self.context = Context::UnsafeBlock(block.hir_id);
|
||||
intravisit::walk_block(self, block);
|
||||
self.context = previous_context;
|
||||
return;
|
||||
}
|
||||
(Context::UnsafeFn(hir_id), Some(AllAllowedInUnsafeFn(lint_root))) => {
|
||||
UnusedUnsafe::InUnsafeFn(hir_id, lint_root)
|
||||
}
|
||||
(Context::UnsafeBlock(hir_id), Some(_)) => UnusedUnsafe::InUnsafeBlock(hir_id),
|
||||
};
|
||||
self.unused_unsafes.push((block.hir_id, unused_unsafe));
|
||||
}
|
||||
intravisit::walk_block(self, block);
|
||||
}
|
||||
|
||||
fn visit_fn(
|
||||
&mut self,
|
||||
fk: intravisit::FnKind<'tcx>,
|
||||
_fd: &'tcx hir::FnDecl<'tcx>,
|
||||
b: hir::BodyId,
|
||||
_s: rustc_span::Span,
|
||||
_id: HirId,
|
||||
) {
|
||||
if matches!(fk, intravisit::FnKind::Closure) {
|
||||
self.visit_body(self.tcx.hir().body(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -405,20 +465,38 @@ impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_> {
|
||||
fn check_unused_unsafe(
|
||||
tcx: TyCtxt<'_>,
|
||||
def_id: LocalDefId,
|
||||
used_unsafe: &FxHashSet<hir::HirId>,
|
||||
unsafe_blocks: &mut Vec<(hir::HirId, bool)>,
|
||||
) {
|
||||
let body_id = tcx.hir().maybe_body_owned_by(tcx.hir().local_def_id_to_hir_id(def_id));
|
||||
used_unsafe_blocks: &FxHashMap<HirId, UsedUnsafeBlockData>,
|
||||
) -> Vec<(HirId, UnusedUnsafe)> {
|
||||
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
|
||||
let body_id = tcx.hir().maybe_body_owned_by(hir_id);
|
||||
|
||||
let Some(body_id) = body_id else {
|
||||
debug!("check_unused_unsafe({:?}) - no body found", def_id);
|
||||
return;
|
||||
return vec![];
|
||||
};
|
||||
let body = tcx.hir().body(body_id);
|
||||
debug!("check_unused_unsafe({:?}, body={:?}, used_unsafe={:?})", def_id, body, used_unsafe);
|
||||
|
||||
let mut visitor = UnusedUnsafeVisitor { used_unsafe, unsafe_blocks };
|
||||
let context = match tcx.hir().fn_sig_by_hir_id(hir_id) {
|
||||
Some(sig) if sig.header.unsafety == hir::Unsafety::Unsafe => Context::UnsafeFn(hir_id),
|
||||
_ => Context::Safe,
|
||||
};
|
||||
|
||||
debug!(
|
||||
"check_unused_unsafe({:?}, context={:?}, body={:?}, used_unsafe_blocks={:?})",
|
||||
def_id, body, context, used_unsafe_blocks
|
||||
);
|
||||
|
||||
let mut unused_unsafes = vec![];
|
||||
|
||||
let mut visitor = UnusedUnsafeVisitor {
|
||||
tcx,
|
||||
used_unsafe_blocks,
|
||||
context,
|
||||
unused_unsafes: &mut unused_unsafes,
|
||||
};
|
||||
intravisit::Visitor::visit_body(&mut visitor, body);
|
||||
|
||||
unused_unsafes
|
||||
}
|
||||
|
||||
fn unsafety_check_result<'tcx>(
|
||||
@ -436,56 +514,52 @@ fn unsafety_check_result<'tcx>(
|
||||
let mut checker = UnsafetyChecker::new(body, def.did, tcx, param_env);
|
||||
checker.visit_body(&body);
|
||||
|
||||
check_unused_unsafe(tcx, def.did, &checker.used_unsafe, &mut checker.inherited_blocks);
|
||||
let unused_unsafes = (!tcx.is_closure(def.did.to_def_id()))
|
||||
.then(|| check_unused_unsafe(tcx, def.did, &checker.used_unsafe_blocks));
|
||||
|
||||
tcx.arena.alloc(UnsafetyCheckResult {
|
||||
violations: checker.violations.into(),
|
||||
unsafe_blocks: checker.inherited_blocks.into(),
|
||||
violations: checker.violations,
|
||||
used_unsafe_blocks: checker.used_unsafe_blocks,
|
||||
unused_unsafes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the `HirId` for an enclosing scope that is also `unsafe`.
|
||||
fn is_enclosed(
|
||||
tcx: TyCtxt<'_>,
|
||||
used_unsafe: &FxHashSet<hir::HirId>,
|
||||
id: hir::HirId,
|
||||
unsafe_op_in_unsafe_fn_allowed: bool,
|
||||
) -> Option<(&'static str, hir::HirId)> {
|
||||
let parent_id = tcx.hir().get_parent_node(id);
|
||||
if parent_id != id {
|
||||
if used_unsafe.contains(&parent_id) {
|
||||
Some(("block", parent_id))
|
||||
} else if let Some(Node::Item(&hir::Item {
|
||||
kind: hir::ItemKind::Fn(ref sig, _, _), ..
|
||||
})) = tcx.hir().find(parent_id)
|
||||
{
|
||||
if sig.header.unsafety == hir::Unsafety::Unsafe && unsafe_op_in_unsafe_fn_allowed {
|
||||
Some(("fn", parent_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
is_enclosed(tcx, used_unsafe, parent_id, unsafe_op_in_unsafe_fn_allowed)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn report_unused_unsafe(tcx: TyCtxt<'_>, used_unsafe: &FxHashSet<hir::HirId>, id: hir::HirId) {
|
||||
fn report_unused_unsafe(tcx: TyCtxt<'_>, kind: UnusedUnsafe, id: HirId) {
|
||||
let span = tcx.sess.source_map().guess_head_span(tcx.hir().span(id));
|
||||
tcx.struct_span_lint_hir(UNUSED_UNSAFE, id, span, |lint| {
|
||||
let msg = "unnecessary `unsafe` block";
|
||||
let mut db = lint.build(msg);
|
||||
db.span_label(span, msg);
|
||||
if let Some((kind, id)) =
|
||||
is_enclosed(tcx, used_unsafe, id, unsafe_op_in_unsafe_fn_allowed(tcx, id))
|
||||
{
|
||||
db.span_label(
|
||||
tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
|
||||
format!("because it's nested under this `unsafe` {}", kind),
|
||||
);
|
||||
match kind {
|
||||
UnusedUnsafe::Unused => {}
|
||||
UnusedUnsafe::InUnsafeBlock(id) => {
|
||||
db.span_label(
|
||||
tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
|
||||
format!("because it's nested under this `unsafe` block"),
|
||||
);
|
||||
}
|
||||
UnusedUnsafe::InUnsafeFn(id, usage_lint_root) => {
|
||||
db.span_label(
|
||||
tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
|
||||
format!("because it's nested under this `unsafe` fn"),
|
||||
)
|
||||
.note(
|
||||
"this `unsafe` block does contain unsafe operations, \
|
||||
but those are already allowed in an `unsafe fn`",
|
||||
);
|
||||
let (level, source) =
|
||||
tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, usage_lint_root);
|
||||
assert_eq!(level, Level::Allow);
|
||||
lint::explain_lint_level_source(
|
||||
tcx.sess,
|
||||
UNSAFE_OP_IN_UNSAFE_FN,
|
||||
Level::Allow,
|
||||
source,
|
||||
&mut db,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
db.emit();
|
||||
});
|
||||
}
|
||||
@ -498,7 +572,7 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||
return;
|
||||
}
|
||||
|
||||
let UnsafetyCheckResult { violations, unsafe_blocks } = tcx.unsafety_check_result(def_id);
|
||||
let UnsafetyCheckResult { violations, unused_unsafes, .. } = tcx.unsafety_check_result(def_id);
|
||||
|
||||
for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() {
|
||||
let (description, note) = details.description_and_note();
|
||||
@ -539,20 +613,8 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
|
||||
}
|
||||
}
|
||||
|
||||
let (mut unsafe_used, mut unsafe_unused): (FxHashSet<_>, Vec<_>) = Default::default();
|
||||
for &(block_id, is_used) in unsafe_blocks.iter() {
|
||||
if is_used {
|
||||
unsafe_used.insert(block_id);
|
||||
} else {
|
||||
unsafe_unused.push(block_id);
|
||||
}
|
||||
}
|
||||
// The unused unsafe blocks might not be in source order; sort them so that the unused unsafe
|
||||
// error messages are properly aligned and the issue-45107 and lint-unused-unsafe tests pass.
|
||||
unsafe_unused.sort_by_cached_key(|hir_id| tcx.hir().span(*hir_id));
|
||||
|
||||
for &block_id in &unsafe_unused {
|
||||
report_unused_unsafe(tcx, &unsafe_used, block_id);
|
||||
for &(block_id, kind) in unused_unsafes.as_ref().unwrap() {
|
||||
report_unused_unsafe(tcx, kind, block_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
61
src/test/ui/span/lint-unused-unsafe-thir.rs
Normal file
61
src/test/ui/span/lint-unused-unsafe-thir.rs
Normal file
@ -0,0 +1,61 @@
|
||||
// FIXME: This file is tracking old lint behavior that's still unchanged in the
|
||||
// unstable -Zthir-unsafeck implementation. See lint-unused-unsafe.rs for more details.
|
||||
//
|
||||
// Exercise the unused_unsafe attribute in some positive and negative cases
|
||||
|
||||
// compile-flags: -Zthir-unsafeck
|
||||
|
||||
#![allow(dead_code)]
|
||||
#![deny(unused_unsafe)]
|
||||
|
||||
|
||||
mod foo {
|
||||
extern "C" {
|
||||
pub fn bar();
|
||||
}
|
||||
}
|
||||
|
||||
fn callback<T, F>(_f: F) -> T where F: FnOnce() -> T { panic!() }
|
||||
unsafe fn unsf() {}
|
||||
|
||||
fn bad1() { unsafe {} } //~ ERROR: unnecessary `unsafe` block
|
||||
fn bad2() { unsafe { bad1() } } //~ ERROR: unnecessary `unsafe` block
|
||||
unsafe fn bad3() { unsafe {} } //~ ERROR: unnecessary `unsafe` block
|
||||
fn bad4() { unsafe { callback(||{}) } } //~ ERROR: unnecessary `unsafe` block
|
||||
unsafe fn bad5() { unsafe { unsf() } } //~ ERROR: unnecessary `unsafe` block
|
||||
fn bad6() {
|
||||
unsafe { // don't put the warning here
|
||||
unsafe { //~ ERROR: unnecessary `unsafe` block
|
||||
unsf()
|
||||
}
|
||||
}
|
||||
}
|
||||
unsafe fn bad7() {
|
||||
unsafe { //~ ERROR: unnecessary `unsafe` block
|
||||
unsafe { //~ ERROR: unnecessary `unsafe` block
|
||||
unsf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn good0() { unsf() }
|
||||
fn good1() { unsafe { unsf() } }
|
||||
fn good2() {
|
||||
/* bug uncovered when implementing warning about unused unsafe blocks. Be
|
||||
sure that when purity is inherited that the source of the unsafe-ness
|
||||
is tracked correctly */
|
||||
unsafe {
|
||||
unsafe fn what() -> Vec<String> { panic!() }
|
||||
|
||||
callback(|| {
|
||||
what();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn good3() { foo::bar() }
|
||||
fn good4() { unsafe { foo::bar() } }
|
||||
|
||||
#[allow(unused_unsafe)] fn allowed() { unsafe {} }
|
||||
|
||||
fn main() {}
|
@ -1,23 +1,23 @@
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:19:13
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:21:13
|
||||
|
|
||||
LL | fn bad1() { unsafe {} }
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
|
||||
note: the lint level is defined here
|
||||
--> $DIR/lint-unused-unsafe.rs:7:9
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:9:9
|
||||
|
|
||||
LL | #![deny(unused_unsafe)]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:20:13
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:22:13
|
||||
|
|
||||
LL | fn bad2() { unsafe { bad1() } }
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:21:20
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:23:20
|
||||
|
|
||||
LL | unsafe fn bad3() { unsafe {} }
|
||||
| ---------------- ^^^^^^ unnecessary `unsafe` block
|
||||
@ -25,13 +25,13 @@ LL | unsafe fn bad3() { unsafe {} }
|
||||
| because it's nested under this `unsafe` fn
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:22:13
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:24:13
|
||||
|
|
||||
LL | fn bad4() { unsafe { callback(||{}) } }
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:23:20
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:25:20
|
||||
|
|
||||
LL | unsafe fn bad5() { unsafe { unsf() } }
|
||||
| ---------------- ^^^^^^ unnecessary `unsafe` block
|
||||
@ -39,7 +39,7 @@ LL | unsafe fn bad5() { unsafe { unsf() } }
|
||||
| because it's nested under this `unsafe` fn
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:26:9
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:28:9
|
||||
|
|
||||
LL | unsafe { // don't put the warning here
|
||||
| ------ because it's nested under this `unsafe` block
|
||||
@ -47,7 +47,7 @@ LL | unsafe {
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:33:9
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:35:9
|
||||
|
|
||||
LL | unsafe {
|
||||
| ------ because it's nested under this `unsafe` block
|
||||
@ -55,7 +55,7 @@ LL | unsafe {
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/lint-unused-unsafe.rs:32:5
|
||||
--> $DIR/lint-unused-unsafe-thir.rs:34:5
|
||||
|
|
||||
LL | unsafe fn bad7() {
|
||||
| ---------------- because it's nested under this `unsafe` fn
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -76,12 +76,10 @@ LL | unsafe {}
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:47:14
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:47:5
|
||||
|
|
||||
LL | unsafe { unsafe { unsf() } }
|
||||
| ------ ^^^^^^ unnecessary `unsafe` block
|
||||
| |
|
||||
| because it's nested under this `unsafe` block
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:58:5
|
||||
@ -91,6 +89,13 @@ LL | unsafe fn allow_level() {
|
||||
...
|
||||
LL | unsafe { unsf() }
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
|
||||
= note: this `unsafe` block does contain unsafe operations, but those are already allowed in an `unsafe fn`
|
||||
note: the lint level is defined here
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:51:9
|
||||
|
|
||||
LL | #[allow(unsafe_op_in_unsafe_fn)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: unnecessary `unsafe` block
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:70:9
|
||||
@ -100,6 +105,13 @@ LL | unsafe fn nested_allow_level() {
|
||||
...
|
||||
LL | unsafe { unsf() }
|
||||
| ^^^^^^ unnecessary `unsafe` block
|
||||
|
|
||||
= note: this `unsafe` block does contain unsafe operations, but those are already allowed in an `unsafe fn`
|
||||
note: the lint level is defined here
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:63:13
|
||||
|
|
||||
LL | #[allow(unsafe_op_in_unsafe_fn)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
error[E0133]: call to unsafe function is unsafe and requires unsafe block
|
||||
--> $DIR/rfc-2585-unsafe_op_in_unsafe_fn.rs:76:5
|
||||
|
Loading…
x
Reference in New Issue
Block a user