Skip to content

Commit 78e7312

Browse files
committed
Auto merge of #7748 - Serial-ATA:lint-undocumented-unsafe, r=flip1995
Add undocumented_unsafe_blocks lint changelog: Added a new lint [`undocumented_unsafe_blocks`] Fixes #7464, #7238 (?)
2 parents 8aff5dd + 412b862 commit 78e7312

7 files changed

+676
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,7 @@ Released 2018-09-13
30333033
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
30343034
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
30353035
[`type_repetition_in_bounds`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_repetition_in_bounds
3036+
[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/master/index.html#undocumented_unsafe_blocks
30363037
[`undropped_manually_drops`]: https://rust-lang.github.io/rust-clippy/master/index.html#undropped_manually_drops
30373038
[`unicode_not_nfc`]: https://rust-lang.github.io/rust-clippy/master/index.html#unicode_not_nfc
30383039
[`unimplemented`]: https://rust-lang.github.io/rust-clippy/master/index.html#unimplemented

clippy_lints/src/lib.register_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ store.register_lints(&[
465465
types::REDUNDANT_ALLOCATION,
466466
types::TYPE_COMPLEXITY,
467467
types::VEC_BOX,
468+
undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS,
468469
undropped_manually_drops::UNDROPPED_MANUALLY_DROPS,
469470
unicode::INVISIBLE_CHARACTERS,
470471
unicode::NON_ASCII_LITERAL,

clippy_lints/src/lib.register_restriction.rs

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ store.register_group(true, "clippy::restriction", Some("clippy_restriction"), ve
5757
LintId::of(strings::STR_TO_STRING),
5858
LintId::of(types::RC_BUFFER),
5959
LintId::of(types::RC_MUTEX),
60+
LintId::of(undocumented_unsafe_blocks::UNDOCUMENTED_UNSAFE_BLOCKS),
6061
LintId::of(unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS),
6162
LintId::of(unwrap_in_result::UNWRAP_IN_RESULT),
6263
LintId::of(verbose_file_reads::VERBOSE_FILE_READS),

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,7 @@ mod transmute;
358358
mod transmuting_null;
359359
mod try_err;
360360
mod types;
361+
mod undocumented_unsafe_blocks;
361362
mod undropped_manually_drops;
362363
mod unicode;
363364
mod unit_return_expecting_ord;
@@ -769,6 +770,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
769770
store.register_late_pass(move || Box::new(if_then_panic::IfThenPanic));
770771
let enable_raw_pointer_heuristic_for_send = conf.enable_raw_pointer_heuristic_for_send;
771772
store.register_late_pass(move || Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(enable_raw_pointer_heuristic_for_send)));
773+
store.register_late_pass(move || Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::default()));
772774
}
773775

774776
#[rustfmt::skip]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
2+
use clippy_utils::source::{indent_of, reindent_multiline, snippet};
3+
use clippy_utils::{in_macro, is_lint_allowed};
4+
use rustc_errors::Applicability;
5+
use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
6+
use rustc_hir::{Block, BlockCheckMode, Expr, ExprKind, HirId, Local, UnsafeSource};
7+
use rustc_lexer::TokenKind;
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::hir::map::Map;
10+
use rustc_middle::lint::in_external_macro;
11+
use rustc_middle::ty::TyCtxt;
12+
use rustc_session::{declare_tool_lint, impl_lint_pass};
13+
use rustc_span::{BytePos, Span};
14+
use std::borrow::Cow;
15+
16+
declare_clippy_lint! {
17+
/// ### What it does
18+
/// Checks for `unsafe` blocks without a `// Safety: ` comment
19+
/// explaining why the unsafe operations performed inside
20+
/// the block are safe.
21+
///
22+
/// ### Why is this bad?
23+
/// Undocumented unsafe blocks can make it difficult to
24+
/// read and maintain code, as well as uncover unsoundness
25+
/// and bugs.
26+
///
27+
/// ### Example
28+
/// ```rust
29+
/// use std::ptr::NonNull;
30+
/// let a = &mut 42;
31+
///
32+
/// let ptr = unsafe { NonNull::new_unchecked(a) };
33+
/// ```
34+
/// Use instead:
35+
/// ```rust
36+
/// use std::ptr::NonNull;
37+
/// let a = &mut 42;
38+
///
39+
/// // Safety: references are guaranteed to be non-null.
40+
/// let ptr = unsafe { NonNull::new_unchecked(a) };
41+
/// ```
42+
pub UNDOCUMENTED_UNSAFE_BLOCKS,
43+
restriction,
44+
"creating an unsafe block without explaining why it is safe"
45+
}
46+
47+
impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS]);
48+
49+
#[derive(Default)]
50+
pub struct UndocumentedUnsafeBlocks {
51+
pub local_level: u32,
52+
pub local_span: Option<Span>,
53+
// The local was already checked for an overall safety comment
54+
// There is no need to continue checking the blocks in the local
55+
pub local_checked: bool,
56+
// Since we can only check the blocks from expanded macros
57+
// We have to omit the suggestion due to the actual definition
58+
// Not being available to us
59+
pub macro_expansion: bool,
60+
}
61+
62+
impl LateLintPass<'_> for UndocumentedUnsafeBlocks {
63+
fn check_block(&mut self, cx: &LateContext<'_>, block: &'_ Block<'_>) {
64+
if_chain! {
65+
if !self.local_checked;
66+
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id);
67+
if !in_external_macro(cx.tcx.sess, block.span);
68+
if let BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) = block.rules;
69+
if let Some(enclosing_scope_hir_id) = cx.tcx.hir().get_enclosing_scope(block.hir_id);
70+
if self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, block.span) == Some(false);
71+
then {
72+
let mut span = block.span;
73+
74+
if let Some(local_span) = self.local_span {
75+
span = local_span;
76+
77+
let result = self.block_has_safety_comment(cx.tcx, enclosing_scope_hir_id, span);
78+
79+
if result.unwrap_or(true) {
80+
self.local_checked = true;
81+
return;
82+
}
83+
}
84+
85+
self.lint(cx, span);
86+
}
87+
}
88+
}
89+
90+
fn check_local(&mut self, cx: &LateContext<'_>, local: &'_ Local<'_>) {
91+
if_chain! {
92+
if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, local.hir_id);
93+
if !in_external_macro(cx.tcx.sess, local.span);
94+
if let Some(init) = local.init;
95+
then {
96+
self.visit_expr(init);
97+
98+
if self.local_level > 0 {
99+
self.local_span = Some(local.span);
100+
}
101+
}
102+
}
103+
}
104+
105+
fn check_block_post(&mut self, _: &LateContext<'_>, _: &'_ Block<'_>) {
106+
self.local_level = self.local_level.saturating_sub(1);
107+
108+
if self.local_level == 0 {
109+
self.local_checked = false;
110+
self.local_span = None;
111+
}
112+
}
113+
}
114+
115+
impl<'hir> Visitor<'hir> for UndocumentedUnsafeBlocks {
116+
type Map = Map<'hir>;
117+
118+
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
119+
NestedVisitorMap::None
120+
}
121+
122+
fn visit_expr(&mut self, ex: &'v Expr<'v>) {
123+
match ex.kind {
124+
ExprKind::Block(_, _) => self.local_level = self.local_level.saturating_add(1),
125+
_ => walk_expr(self, ex),
126+
}
127+
}
128+
}
129+
130+
impl UndocumentedUnsafeBlocks {
131+
fn block_has_safety_comment(&mut self, tcx: TyCtxt<'_>, enclosing_hir_id: HirId, block_span: Span) -> Option<bool> {
132+
let map = tcx.hir();
133+
let source_map = tcx.sess.source_map();
134+
135+
let enclosing_scope_span = map.opt_span(enclosing_hir_id)?;
136+
137+
let between_span = if in_macro(block_span) {
138+
self.macro_expansion = true;
139+
enclosing_scope_span.with_hi(block_span.hi())
140+
} else {
141+
self.macro_expansion = false;
142+
enclosing_scope_span.to(block_span)
143+
};
144+
145+
let file_name = source_map.span_to_filename(between_span);
146+
let source_file = source_map.get_source_file(&file_name)?;
147+
148+
let lex_start = (between_span.lo().0 + 1) as usize;
149+
let src_str = source_file.src.as_ref()?[lex_start..between_span.hi().0 as usize].to_string();
150+
151+
let mut pos = 0;
152+
let mut comment = false;
153+
154+
for token in rustc_lexer::tokenize(&src_str) {
155+
match token.kind {
156+
TokenKind::LineComment { doc_style: None }
157+
| TokenKind::BlockComment {
158+
doc_style: None,
159+
terminated: true,
160+
} => {
161+
let comment_str = src_str[pos + 2..pos + token.len].to_ascii_uppercase();
162+
163+
if comment_str.contains("SAFETY:") {
164+
comment = true;
165+
}
166+
},
167+
// We need to add all whitespace to `pos` before checking the comment's line number
168+
TokenKind::Whitespace => {},
169+
_ => {
170+
if comment {
171+
// Get the line number of the "comment" (really wherever the trailing whitespace ended)
172+
let comment_line_num = source_file
173+
.lookup_file_pos_with_col_display(BytePos((lex_start + pos).try_into().unwrap()))
174+
.0;
175+
// Find the block/local's line number
176+
let block_line_num = tcx.sess.source_map().lookup_char_pos(block_span.lo()).line;
177+
178+
// Check the comment is immediately followed by the block/local
179+
if block_line_num == comment_line_num + 1 || block_line_num == comment_line_num {
180+
return Some(true);
181+
}
182+
183+
comment = false;
184+
}
185+
},
186+
}
187+
188+
pos += token.len;
189+
}
190+
191+
Some(false)
192+
}
193+
194+
fn lint(&self, cx: &LateContext<'_>, mut span: Span) {
195+
let source_map = cx.tcx.sess.source_map();
196+
197+
if source_map.is_multiline(span) {
198+
span = source_map.span_until_char(span, '\n');
199+
}
200+
201+
if self.macro_expansion {
202+
span_lint_and_help(
203+
cx,
204+
UNDOCUMENTED_UNSAFE_BLOCKS,
205+
span,
206+
"unsafe block in macro expansion missing a safety comment",
207+
None,
208+
"consider adding a safety comment in the macro definition",
209+
);
210+
} else {
211+
let block_indent = indent_of(cx, span);
212+
let suggestion = format!("// Safety: ...\n{}", snippet(cx, span, ".."));
213+
214+
span_lint_and_sugg(
215+
cx,
216+
UNDOCUMENTED_UNSAFE_BLOCKS,
217+
span,
218+
"unsafe block missing a safety comment",
219+
"consider adding a safety comment",
220+
reindent_multiline(Cow::Borrowed(&suggestion), true, block_indent).to_string(),
221+
Applicability::HasPlaceholders,
222+
);
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)