Skip to content

Commit 9411232

Browse files
committed
Add explanation to unused_unsafe warning when the unsafe_op_in_unsafe_fn lint status is relevant
1 parent 6a700f9 commit 9411232

File tree

6 files changed

+312
-106
lines changed

6 files changed

+312
-106
lines changed

compiler/rustc_middle/src/lint.rs

Lines changed: 73 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,77 @@ impl<'a> LintDiagnosticBuilder<'a> {
202202
}
203203
}
204204

205+
pub fn explain_lint_level_source<'s>(
206+
sess: &'s Session,
207+
lint: &'static Lint,
208+
level: Level,
209+
src: LintLevelSource,
210+
err: &mut DiagnosticBuilder<'s>,
211+
) {
212+
let name = lint.name_lower();
213+
match src {
214+
LintLevelSource::Default => {
215+
sess.diag_note_once(
216+
err,
217+
DiagnosticMessageId::from(lint),
218+
&format!("`#[{}({})]` on by default", level.as_str(), name),
219+
);
220+
}
221+
LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
222+
let flag = match orig_level {
223+
Level::Warn => "-W",
224+
Level::Deny => "-D",
225+
Level::Forbid => "-F",
226+
Level::Allow => "-A",
227+
Level::ForceWarn => "--force-warn",
228+
};
229+
let hyphen_case_lint_name = name.replace('_', "-");
230+
if lint_flag_val.as_str() == name {
231+
sess.diag_note_once(
232+
err,
233+
DiagnosticMessageId::from(lint),
234+
&format!(
235+
"requested on the command line with `{} {}`",
236+
flag, hyphen_case_lint_name
237+
),
238+
);
239+
} else {
240+
let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
241+
sess.diag_note_once(
242+
err,
243+
DiagnosticMessageId::from(lint),
244+
&format!(
245+
"`{} {}` implied by `{} {}`",
246+
flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
247+
),
248+
);
249+
}
250+
}
251+
LintLevelSource::Node(lint_attr_name, src, reason) => {
252+
if let Some(rationale) = reason {
253+
err.note(rationale.as_str());
254+
}
255+
sess.diag_span_note_once(
256+
err,
257+
DiagnosticMessageId::from(lint),
258+
src,
259+
"the lint level is defined here",
260+
);
261+
if lint_attr_name.as_str() != name {
262+
let level_str = level.as_str();
263+
sess.diag_note_once(
264+
err,
265+
DiagnosticMessageId::from(lint),
266+
&format!(
267+
"`#[{}({})]` implied by `#[{}({})]`",
268+
level_str, name, level_str, lint_attr_name
269+
),
270+
);
271+
}
272+
}
273+
}
274+
}
275+
205276
pub fn struct_lint_level<'s, 'd>(
206277
sess: &'s Session,
207278
lint: &'static Lint,
@@ -278,69 +349,9 @@ pub fn struct_lint_level<'s, 'd>(
278349
}
279350
}
280351

281-
let name = lint.name_lower();
282-
match src {
283-
LintLevelSource::Default => {
284-
sess.diag_note_once(
285-
&mut err,
286-
DiagnosticMessageId::from(lint),
287-
&format!("`#[{}({})]` on by default", level.as_str(), name),
288-
);
289-
}
290-
LintLevelSource::CommandLine(lint_flag_val, orig_level) => {
291-
let flag = match orig_level {
292-
Level::Warn => "-W",
293-
Level::Deny => "-D",
294-
Level::Forbid => "-F",
295-
Level::Allow => "-A",
296-
Level::ForceWarn => "--force-warn",
297-
};
298-
let hyphen_case_lint_name = name.replace('_', "-");
299-
if lint_flag_val.as_str() == name {
300-
sess.diag_note_once(
301-
&mut err,
302-
DiagnosticMessageId::from(lint),
303-
&format!(
304-
"requested on the command line with `{} {}`",
305-
flag, hyphen_case_lint_name
306-
),
307-
);
308-
} else {
309-
let hyphen_case_flag_val = lint_flag_val.as_str().replace('_', "-");
310-
sess.diag_note_once(
311-
&mut err,
312-
DiagnosticMessageId::from(lint),
313-
&format!(
314-
"`{} {}` implied by `{} {}`",
315-
flag, hyphen_case_lint_name, flag, hyphen_case_flag_val
316-
),
317-
);
318-
}
319-
}
320-
LintLevelSource::Node(lint_attr_name, src, reason) => {
321-
if let Some(rationale) = reason {
322-
err.note(rationale.as_str());
323-
}
324-
sess.diag_span_note_once(
325-
&mut err,
326-
DiagnosticMessageId::from(lint),
327-
src,
328-
"the lint level is defined here",
329-
);
330-
if lint_attr_name.as_str() != name {
331-
let level_str = level.as_str();
332-
sess.diag_note_once(
333-
&mut err,
334-
DiagnosticMessageId::from(lint),
335-
&format!(
336-
"`#[{}({})]` implied by `#[{}({})]`",
337-
level_str, name, level_str, lint_attr_name
338-
),
339-
);
340-
}
341-
}
342-
}
352+
explain_lint_level_source(sess, lint, level, src, &mut err);
343353

354+
let name = lint.name_lower();
344355
let is_force_warn = matches!(level, Level::ForceWarn);
345356
err.code(DiagnosticId::Lint { name, has_future_breakage, is_force_warn });
346357

compiler/rustc_middle/src/mir/query.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,20 @@ pub enum UnusedUnsafe {
124124
InUnsafeBlock(hir::HirId),
125125
// `unsafe` block nested under `unsafe` fn
126126
// ``… because it's nested under this `unsafe` fn``
127-
InUnsafeFn(hir::HirId),
127+
//
128+
// the second HirId here indicates the first usage of the `unsafe` block,
129+
// which allows retrival of the LintLevelSource for why that operation would
130+
// have been permitted without the block
131+
InUnsafeFn(hir::HirId, hir::HirId),
132+
}
133+
134+
// The `Ord` implementation is relevant, because `SomeDisallowedInUnsafeFn` takes
135+
// precedence and we want the smallest `HirId`
136+
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, TyEncodable, TyDecodable, HashStable, Debug)]
137+
pub enum UsedUnsafeBlockData {
138+
SomeDisallowedInUnsafeFn,
139+
// the HirId here indicates the first usage of the `unsafe` block
140+
AllAllowedInUnsafeFn(hir::HirId),
128141
}
129142

130143
#[derive(TyEncodable, TyDecodable, HashStable, Debug)]
@@ -134,9 +147,9 @@ pub struct UnsafetyCheckResult {
134147

135148
/// Used `unsafe` blocks in this function. This is used for the "unused_unsafe" lint.
136149
///
137-
/// The keys are the used `unsafe` blocks, the boolean flag indicates whether
150+
/// The keys are the used `unsafe` blocks, the UnusedUnsafeKind indicates whether
138151
/// or not any of the usages happen at a place that doesn't allow `unsafe_op_in_unsafe_fn`.
139-
pub used_unsafe_blocks: FxHashMap<hir::HirId, bool>,
152+
pub used_unsafe_blocks: FxHashMap<hir::HirId, UsedUnsafeBlockData>,
140153

141154
/// This is `Some` iff the item is not a closure.
142155
pub unused_unsafe: Option<Vec<(hir::HirId, UnusedUnsafe)>>,

compiler/rustc_mir_transform/src/check_unsafety.rs

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ use rustc_hir::hir_id::HirId;
66
use rustc_hir::intravisit;
77
use rustc_hir::Node;
88
use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
9-
use rustc_middle::mir::*;
109
use rustc_middle::ty::query::Providers;
1110
use rustc_middle::ty::{self, TyCtxt};
11+
use rustc_middle::{lint, mir::*};
1212
use rustc_session::lint::builtin::{UNSAFE_OP_IN_UNSAFE_FN, UNUSED_UNSAFE};
1313
use rustc_session::lint::Level;
1414

15-
use std::iter;
15+
use std::collections::hash_map;
1616
use std::ops::Bound;
17+
use std::{cmp, iter};
1718

1819
pub struct UnsafetyChecker<'a, 'tcx> {
1920
body: &'a Body<'tcx>,
@@ -25,9 +26,9 @@ pub struct UnsafetyChecker<'a, 'tcx> {
2526

2627
/// Used `unsafe` blocks in this function. This is used for the "unused_unsafe" lint.
2728
///
28-
/// The keys are the used `unsafe` blocks, the boolean flag indicates whether
29+
/// The keys are the used `unsafe` blocks, the UnusedUnsafeKind indicates whether
2930
/// or not any of the usages happen at a place that doesn't allow `unsafe_op_in_unsafe_fn`.
30-
used_unsafe_blocks: FxHashMap<HirId, bool>,
31+
used_unsafe_blocks: FxHashMap<HirId, UsedUnsafeBlockData>,
3132
}
3233

3334
impl<'a, 'tcx> UnsafetyChecker<'a, 'tcx> {
@@ -262,8 +263,21 @@ impl<'tcx> UnsafetyChecker<'_, 'tcx> {
262263
fn register_violations<'a>(
263264
&mut self,
264265
violations: impl ExactSizeIterator<Item = &'a UnsafetyViolation>,
265-
new_used_unsafe_blocks: impl Iterator<Item = (&'a HirId, &'a bool)>,
266+
new_used_unsafe_blocks: impl Iterator<Item = (&'a HirId, &'a UsedUnsafeBlockData)>,
266267
) {
268+
use UsedUnsafeBlockData::{AllAllowedInUnsafeFn, SomeDisallowedInUnsafeFn};
269+
270+
let update_entry = |this: &mut Self, hir_id, new_usage| {
271+
match this.used_unsafe_blocks.entry(hir_id) {
272+
hash_map::Entry::Occupied(mut entry) => {
273+
let entry = entry.get_mut();
274+
*entry = cmp::min(*entry, new_usage);
275+
}
276+
hash_map::Entry::Vacant(entry) => {
277+
entry.insert(new_usage);
278+
}
279+
};
280+
};
267281
let safety = self.body.source_scopes[self.source_info.scope]
268282
.local_data
269283
.as_ref()
@@ -290,23 +304,21 @@ impl<'tcx> UnsafetyChecker<'_, 'tcx> {
290304
}
291305
}),
292306
Safety::BuiltinUnsafe => {}
293-
Safety::ExplicitUnsafe(hir_id) => {
294-
let used = violations.len() != 0;
295-
let disallowed_in_unsafe_fn = { violations }.any(|violation| {
296-
self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, violation.lint_root).0
297-
!= Level::Allow
298-
});
299-
// mark unsafe block as used if there are any unsafe operations inside
300-
if used {
301-
*self.used_unsafe_blocks.entry(hir_id).or_insert(false) |=
302-
disallowed_in_unsafe_fn;
303-
}
304-
}
307+
Safety::ExplicitUnsafe(hir_id) => violations.for_each(|violation| {
308+
update_entry(
309+
self,
310+
hir_id,
311+
match self.tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, violation.lint_root).0
312+
{
313+
Level::Allow => AllAllowedInUnsafeFn(violation.lint_root),
314+
_ => SomeDisallowedInUnsafeFn,
315+
},
316+
)
317+
}),
305318
};
306319

307-
new_used_unsafe_blocks.for_each(|(&hir_id, &disallowed_in_unsafe_fn)| {
308-
*self.used_unsafe_blocks.entry(hir_id).or_insert(false) |= disallowed_in_unsafe_fn;
309-
});
320+
new_used_unsafe_blocks
321+
.for_each(|(&hir_id, &usage_data)| update_entry(self, hir_id, usage_data));
310322
}
311323
fn check_mut_borrowing_layout_constrained_field(
312324
&mut self,
@@ -402,31 +414,36 @@ enum Context {
402414

403415
struct UnusedUnsafeVisitor<'a, 'tcx> {
404416
tcx: TyCtxt<'tcx>,
405-
used_unsafe_blocks: &'a FxHashMap<HirId, bool>,
417+
used_unsafe_blocks: &'a FxHashMap<HirId, UsedUnsafeBlockData>,
406418
context: Context,
407419
unused_unsafe: &'a mut Vec<(HirId, UnusedUnsafe)>,
408420
}
409421

410422
impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> {
411423
fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) {
424+
use UsedUnsafeBlockData::{AllAllowedInUnsafeFn, SomeDisallowedInUnsafeFn};
425+
412426
if let hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided) = block.rules {
413427
let used = match self.tcx.lint_level_at_node(UNUSED_UNSAFE, block.hir_id) {
414-
(Level::Allow, _) => Some(true),
428+
(Level::Allow, _) => Some(SomeDisallowedInUnsafeFn),
415429
_ => self.used_unsafe_blocks.get(&block.hir_id).copied(),
416430
};
417431
// only pushes if the contained `match` doesn't `return` early
418432
self.unused_unsafe.push((
419433
block.hir_id,
420434
match (self.context, used) {
421435
(_, None) => UnusedUnsafe::Unused,
422-
(Context::Safe, Some(_)) | (Context::UnsafeFn(_), Some(true)) => {
436+
(Context::Safe, Some(_))
437+
| (Context::UnsafeFn(_), Some(SomeDisallowedInUnsafeFn)) => {
423438
let previous_context = self.context;
424439
self.context = Context::UnsafeBlock(block.hir_id);
425440
intravisit::walk_block(self, block);
426441
self.context = previous_context;
427442
return;
428443
}
429-
(Context::UnsafeFn(hir_id), Some(false)) => UnusedUnsafe::InUnsafeFn(hir_id),
444+
(Context::UnsafeFn(hir_id), Some(AllAllowedInUnsafeFn(lint_root))) => {
445+
UnusedUnsafe::InUnsafeFn(hir_id, lint_root)
446+
}
430447
(Context::UnsafeBlock(hir_id), Some(_)) => UnusedUnsafe::InUnsafeBlock(hir_id),
431448
},
432449
));
@@ -451,7 +468,7 @@ impl<'tcx> intravisit::Visitor<'tcx> for UnusedUnsafeVisitor<'_, 'tcx> {
451468
fn check_unused_unsafe(
452469
tcx: TyCtxt<'_>,
453470
def_id: LocalDefId,
454-
used_unsafe_blocks: &FxHashMap<HirId, bool>,
471+
used_unsafe_blocks: &FxHashMap<HirId, UsedUnsafeBlockData>,
455472
) -> Vec<(HirId, UnusedUnsafe)> {
456473
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
457474
let body_id = tcx.hir().maybe_body_owned_by(hir_id);
@@ -527,13 +544,18 @@ fn report_unused_unsafe(tcx: TyCtxt<'_>, kind: UnusedUnsafe, id: HirId) {
527544
format!("because it's nested under this `unsafe` block"),
528545
);
529546
}
530-
UnusedUnsafe::InUnsafeFn(id) => {
547+
UnusedUnsafe::InUnsafeFn(id, usage_lint_root) => {
531548
db.span_label(
532549
tcx.sess.source_map().guess_head_span(tcx.hir().span(id)),
533550
format!("because it's nested under this `unsafe` fn"),
534551
);
552+
db.note("this `unsafe` block does contain unsafe operations, but those are already allowed in an `unsafe` fn");
553+
let (level, source) = tcx.lint_level_at_node(UNSAFE_OP_IN_UNSAFE_FN, usage_lint_root);
554+
assert_eq!(level, Level::Allow);
555+
lint::explain_lint_level_source(tcx.sess, UNSAFE_OP_IN_UNSAFE_FN, Level::Allow, source, &mut db);
535556
}
536557
}
558+
537559
db.emit();
538560
});
539561
}

0 commit comments

Comments
 (0)