Skip to content

Commit 22cfdfa

Browse files
committed
Try to improve error messages on unsafe fns
1 parent c44ea62 commit 22cfdfa

9 files changed

+318
-33
lines changed

clippy_lints/src/undocumented_unsafe_blocks.rs

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use clippy_utils::is_lint_allowed;
77
use clippy_utils::source::walk_span_to_context;
88
use clippy_utils::visitors::{Descend, for_each_expr};
99
use hir::HirId;
10-
use rustc_hir as hir;
11-
use rustc_hir::{Block, BlockCheckMode, Impl, ItemKind, Node, UnsafeSource};
10+
use rustc_errors::Applicability;
11+
use rustc_hir::{self as hir, Block, BlockCheckMode, FnSig, Impl, ItemKind, Node, UnsafeSource};
1212
use rustc_lexer::{FrontmatterAllowed, TokenKind, tokenize};
1313
use rustc_lint::{LateContext, LateLintPass, LintContext};
1414
use rustc_session::impl_lint_pass;
@@ -143,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
143143
if let Some(tail) = block.expr
144144
&& !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
145145
&& !tail.span.in_external_macro(cx.tcx.sess.source_map())
146-
&& let HasSafetyComment::Yes(pos) =
146+
&& let HasSafetyComment::Yes(pos, _) =
147147
stmt_has_safety_comment(cx, tail.span, tail.hir_id, self.accept_comment_above_attributes)
148148
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
149149
{
@@ -168,7 +168,7 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
168168
};
169169
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
170170
&& !stmt.span.in_external_macro(cx.tcx.sess.source_map())
171-
&& let HasSafetyComment::Yes(pos) =
171+
&& let HasSafetyComment::Yes(pos, _) =
172172
stmt_has_safety_comment(cx, stmt.span, stmt.hir_id, self.accept_comment_above_attributes)
173173
&& let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
174174
{
@@ -203,14 +203,14 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
203203

204204
let item_has_safety_comment = item_has_safety_comment(cx, item, self.accept_comment_above_attributes);
205205
match item_has_safety_comment {
206-
HasSafetyComment::Yes(pos) => check_has_safety_comment(cx, item, mk_spans(pos)),
206+
HasSafetyComment::Yes(pos, is_doc) => check_has_safety_comment(cx, item, mk_spans(pos), is_doc),
207207
HasSafetyComment::No => check_has_no_safety_comment(cx, item),
208208
HasSafetyComment::Maybe => {},
209209
}
210210
}
211211
}
212212

213-
fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span)) {
213+
fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, help_span): (Span, Span), is_doc: bool) {
214214
match &item.kind {
215215
ItemKind::Impl(Impl {
216216
of_trait: Some(of_trait),
@@ -252,6 +252,50 @@ fn check_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>, (span, h
252252
}
253253
}
254254
},
255+
// Unsafe functions with a SAFETY comment are suggested to change it to a `# Safety` comment
256+
ItemKind::Fn {
257+
sig: FnSig { header, .. },
258+
..
259+
} if header.is_unsafe() => {
260+
if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
261+
span_lint_and_then(
262+
cx,
263+
UNNECESSARY_SAFETY_COMMENT,
264+
span,
265+
format!(
266+
"{} has safety comment, but maybe a `# Safety` segment would be better",
267+
cx.tcx.def_descr(item.owner_id.to_def_id()),
268+
),
269+
|diag| {
270+
if is_doc {
271+
// If it's already within a doc comment, we try to suggest the change
272+
273+
let source_map = cx.sess().source_map();
274+
if let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
275+
&& let Some(src) = unsafe_line.sf.src.as_deref()
276+
{
277+
let help_pos_lo = source_map.lookup_byte_offset(help_span.lo());
278+
let help_pos_hi = source_map.lookup_byte_offset(help_span.hi());
279+
280+
diag.span_suggestion(
281+
help_span,
282+
"consider changing it to a `# Safety` section",
283+
src.get(help_pos_lo.pos.to_usize()..help_pos_hi.pos.to_usize())
284+
.unwrap()
285+
.replace("SAFETY:", "# Safety"),
286+
Applicability::MachineApplicable,
287+
);
288+
}
289+
} else {
290+
diag.span_help(
291+
help_span,
292+
"consider changing the `SAFETY` comment for a `# Safety` doc comment",
293+
);
294+
}
295+
},
296+
);
297+
}
298+
},
255299
// Aside from unsafe impls and consts/statics with an unsafe block, items in general
256300
// do not have safety invariants that need to be documented, so lint those.
257301
_ => {
@@ -453,16 +497,22 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abo
453497

454498
matches!(
455499
span_from_macro_expansion_has_safety_comment(cx, span, accept_comment_above_attributes),
456-
HasSafetyComment::Yes(_)
500+
HasSafetyComment::Yes(_, _)
457501
) || span_has_safety_comment(cx, span, accept_comment_above_attributes)
458502
}
459503

504+
#[derive(Debug)]
460505
enum HasSafetyComment {
461-
Yes(BytePos),
506+
Yes(BytePos, bool),
462507
No,
463508
Maybe,
464509
}
465510

511+
enum SafetyComment {
512+
Present { pos: BytePos, is_doc: bool },
513+
Absent,
514+
}
515+
466516
/// Checks if the lines immediately preceding the item contain a safety comment.
467517
#[allow(clippy::collapsible_match)]
468518
fn item_has_safety_comment(
@@ -522,8 +572,8 @@ fn item_has_safety_comment(
522572
unsafe_line.sf.start_pos,
523573
accept_comment_above_attributes,
524574
) {
525-
Some(b) => HasSafetyComment::Yes(b),
526-
None => HasSafetyComment::No,
575+
SafetyComment::Present { pos, is_doc } => HasSafetyComment::Yes(pos, is_doc),
576+
SafetyComment::Absent => HasSafetyComment::No,
527577
}
528578
};
529579
}
@@ -568,8 +618,8 @@ fn stmt_has_safety_comment(
568618
unsafe_line.sf.start_pos,
569619
accept_comment_above_attributes,
570620
) {
571-
Some(b) => HasSafetyComment::Yes(b),
572-
None => HasSafetyComment::No,
621+
SafetyComment::Present { pos, is_doc } => HasSafetyComment::Yes(pos, is_doc),
622+
SafetyComment::Absent => HasSafetyComment::No,
573623
}
574624
};
575625
}
@@ -649,8 +699,8 @@ fn span_from_macro_expansion_has_safety_comment(
649699
unsafe_line.sf.start_pos,
650700
accept_comment_above_attributes,
651701
) {
652-
Some(b) => HasSafetyComment::Yes(b),
653-
None => HasSafetyComment::No,
702+
SafetyComment::Present { pos, is_doc } => HasSafetyComment::Yes(pos, is_doc),
703+
SafetyComment::Absent => HasSafetyComment::No,
654704
}
655705
} else {
656706
HasSafetyComment::No
@@ -710,13 +760,15 @@ fn span_has_safety_comment(cx: &LateContext<'_>, span: Span, accept_comment_abov
710760
// fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
711761
// ^-------------^
712762
body_line.line < unsafe_line.line
713-
&& text_has_safety_comment(
714-
src,
715-
&unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line],
716-
unsafe_line.sf.start_pos,
717-
accept_comment_above_attributes,
763+
&& matches!(
764+
text_has_safety_comment(
765+
src,
766+
&unsafe_line.sf.lines()[body_line.line + 1..=unsafe_line.line],
767+
unsafe_line.sf.start_pos,
768+
accept_comment_above_attributes,
769+
),
770+
SafetyComment::Present { .. }
718771
)
719-
.is_some()
720772
} else {
721773
// Problem getting source text. Pretend a comment was found.
722774
true
@@ -735,7 +787,7 @@ fn text_has_safety_comment(
735787
line_starts: &[RelativeBytePos],
736788
start_pos: BytePos,
737789
accept_comment_above_attributes: bool,
738-
) -> Option<BytePos> {
790+
) -> SafetyComment {
739791
let mut lines = line_starts
740792
.array_windows::<2>()
741793
.rev()
@@ -748,7 +800,10 @@ fn text_has_safety_comment(
748800
})
749801
.filter(|(_, text)| !(text.is_empty() || (accept_comment_above_attributes && is_attribute(text))));
750802

751-
let (line_start, line) = lines.next()?;
803+
let Some((line_start, line)) = lines.next() else {
804+
return SafetyComment::Absent;
805+
};
806+
752807
let mut in_codeblock = false;
753808
// Check for a sequence of line comments.
754809
if line.starts_with("//") {
@@ -761,12 +816,15 @@ fn text_has_safety_comment(
761816
in_codeblock = !in_codeblock;
762817
}
763818

764-
if line.to_ascii_uppercase().contains("SAFETY:") && !in_codeblock {
765-
return Some(start_pos + BytePos(u32::try_from(line_start).unwrap()));
819+
if !in_codeblock && line.to_ascii_uppercase().contains("SAFETY:") {
820+
return SafetyComment::Present {
821+
pos: start_pos + BytePos(u32::try_from(line_start).unwrap()),
822+
is_doc: line.starts_with("///"),
823+
};
766824
}
767825
match lines.next() {
768826
Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s),
769-
_ => return None,
827+
_ => return SafetyComment::Absent,
770828
}
771829
}
772830
}
@@ -777,15 +835,22 @@ fn text_has_safety_comment(
777835
if line.starts_with("/*") {
778836
let src = &src[line_start..line_starts.last().unwrap().to_usize()];
779837
let mut tokens = tokenize(src, FrontmatterAllowed::No);
780-
return (src[..tokens.next().unwrap().len as usize]
781-
.to_ascii_uppercase()
782-
.contains("SAFETY:")
783-
&& tokens.all(|t| t.kind == TokenKind::Whitespace))
784-
.then_some(start_pos + BytePos(u32::try_from(line_start).unwrap()));
838+
let a = tokens.next();
839+
if let Some(safety_pos) = src[..a.unwrap().len as usize].to_ascii_uppercase().find("SAFETY:")
840+
&& tokens.all(|t| t.kind == TokenKind::Whitespace)
841+
{
842+
return SafetyComment::Present {
843+
pos: start_pos
844+
+ BytePos(u32::try_from(line_start).unwrap())
845+
+ BytePos(u32::try_from(safety_pos).unwrap()),
846+
is_doc: line.starts_with("/**"),
847+
};
848+
}
849+
return SafetyComment::Absent;
785850
}
786851
match lines.next() {
787852
Some(x) => (line_start, line) = x,
788-
None => return None,
853+
None => return SafetyComment::Absent,
789854
}
790855
}
791856
}

tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.default.stderr

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,5 +426,53 @@ help: consider removing the safety comment
426426
LL | // SAFETY: ...
427427
| ^^^^^^^^^^^^^^
428428

429-
error: aborting due to 46 previous errors
429+
error: function has safety comment, but maybe a `# Safety` segment would be better
430+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5
431+
|
432+
LL | unsafe fn unsafe_comment() {}
433+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
434+
|
435+
help: consider changing the `SAFETY` comment for a `# Safety` doc comment
436+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:5
437+
|
438+
LL | // SAFETY: Bla
439+
| ^^^^^^^^^^^^^^
440+
441+
error: function has safety comment, but maybe a `# Safety` segment would be better
442+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5
443+
|
444+
LL | unsafe fn unsafe_block_comment() {}
445+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
446+
|
447+
help: consider changing the `SAFETY` comment for a `# Safety` doc comment
448+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8
449+
|
450+
LL | SAFETY: Bla
451+
| ^^^^^^^^^^^
452+
453+
error: function has unnecessary safety comment
454+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5
455+
|
456+
LL | fn safe_comment() {}
457+
| ^^^^^^^^^^^^^^^^^^^^
458+
|
459+
help: consider removing the safety comment
460+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:5
461+
|
462+
LL | // SAFETY: Bla
463+
| ^^^^^^^^^^^^^^
464+
465+
error: function has unnecessary safety comment
466+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5
467+
|
468+
LL | fn safe_doc_comment() {}
469+
| ^^^^^^^^^^^^^^^^^^^^^^^^
470+
|
471+
help: consider removing the safety comment
472+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:5
473+
|
474+
LL | /// SAFETY: Bla
475+
| ^^^^^^^^^^^^^^^
476+
477+
error: aborting due to 50 previous errors
430478

tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.disabled.stderr

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,5 +490,53 @@ LL | unsafe {}
490490
|
491491
= help: consider adding a safety comment on the preceding line
492492

493-
error: aborting due to 56 previous errors
493+
error: function has safety comment, but maybe a `# Safety` segment would be better
494+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:781:5
495+
|
496+
LL | unsafe fn unsafe_comment() {}
497+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
498+
|
499+
help: consider changing the `SAFETY` comment for a `# Safety` doc comment
500+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:780:5
501+
|
502+
LL | // SAFETY: Bla
503+
| ^^^^^^^^^^^^^^
504+
505+
error: function has safety comment, but maybe a `# Safety` segment would be better
506+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:787:5
507+
|
508+
LL | unsafe fn unsafe_block_comment() {}
509+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
510+
|
511+
help: consider changing the `SAFETY` comment for a `# Safety` doc comment
512+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:785:8
513+
|
514+
LL | SAFETY: Bla
515+
| ^^^^^^^^^^^
516+
517+
error: function has unnecessary safety comment
518+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:791:5
519+
|
520+
LL | fn safe_comment() {}
521+
| ^^^^^^^^^^^^^^^^^^^^
522+
|
523+
help: consider removing the safety comment
524+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:790:5
525+
|
526+
LL | // SAFETY: Bla
527+
| ^^^^^^^^^^^^^^
528+
529+
error: function has unnecessary safety comment
530+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:795:5
531+
|
532+
LL | fn safe_doc_comment() {}
533+
| ^^^^^^^^^^^^^^^^^^^^^^^^
534+
|
535+
help: consider removing the safety comment
536+
--> tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs:794:5
537+
|
538+
LL | /// SAFETY: Bla
539+
| ^^^^^^^^^^^^^^^
540+
541+
error: aborting due to 60 previous errors
494542

tests/ui-toml/undocumented_unsafe_blocks/undocumented_unsafe_blocks.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,4 +776,24 @@ mod issue_15754 {
776776
}
777777
}
778778

779+
mod unsafe_fns {
780+
// SAFETY: Bla
781+
unsafe fn unsafe_comment() {}
782+
//~^ unnecessary_safety_comment
783+
784+
/*
785+
SAFETY: Bla
786+
*/
787+
unsafe fn unsafe_block_comment() {}
788+
//~^ unnecessary_safety_comment
789+
790+
// SAFETY: Bla
791+
fn safe_comment() {}
792+
//~^ unnecessary_safety_comment
793+
794+
/// SAFETY: Bla
795+
fn safe_doc_comment() {}
796+
//~^ unnecessary_safety_comment
797+
}
798+
779799
fn main() {}

0 commit comments

Comments
 (0)