]> git.proxmox.com Git - rustc.git/blame - src/tools/clippy/clippy_lints/src/undocumented_unsafe_blocks.rs
New upstream version 1.72.1+dfsg1
[rustc.git] / src / tools / clippy / clippy_lints / src / undocumented_unsafe_blocks.rs
CommitLineData
487cf647
FG
1use std::ops::ControlFlow;
2
04454e1e 3use clippy_utils::diagnostics::span_lint_and_help;
04454e1e 4use clippy_utils::source::walk_span_to_context;
487cf647 5use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
923072b8 6use clippy_utils::{get_parent_node, is_lint_allowed};
487cf647 7use hir::HirId;
04454e1e 8use rustc_data_structures::sync::Lrc;
923072b8
FG
9use rustc_hir as hir;
10use rustc_hir::{Block, BlockCheckMode, ItemKind, Node, UnsafeSource};
04454e1e
FG
11use rustc_lexer::{tokenize, TokenKind};
12use rustc_lint::{LateContext, LateLintPass, LintContext};
3c0e092e 13use rustc_middle::lint::in_external_macro;
fe692bf9 14use rustc_session::{declare_tool_lint, impl_lint_pass};
923072b8 15use rustc_span::{BytePos, Pos, Span, SyntaxContext};
3c0e092e
XL
16
17declare_clippy_lint! {
18 /// ### What it does
923072b8 19 /// Checks for `unsafe` blocks and impls without a `// SAFETY: ` comment
3c0e092e
XL
20 /// explaining why the unsafe operations performed inside
21 /// the block are safe.
22 ///
04454e1e
FG
23 /// Note the comment must appear on the line(s) preceding the unsafe block
24 /// with nothing appearing in between. The following is ok:
25 /// ```ignore
26 /// foo(
27 /// // SAFETY:
28 /// // This is a valid safety comment
29 /// unsafe { *x }
30 /// )
31 /// ```
32 /// But neither of these are:
33 /// ```ignore
34 /// // SAFETY:
35 /// // This is not a valid safety comment
36 /// foo(
37 /// /* SAFETY: Neither is this */ unsafe { *x },
38 /// );
39 /// ```
40 ///
3c0e092e 41 /// ### Why is this bad?
923072b8 42 /// Undocumented unsafe blocks and impls can make it difficult to
3c0e092e
XL
43 /// read and maintain code, as well as uncover unsoundness
44 /// and bugs.
45 ///
46 /// ### Example
47 /// ```rust
48 /// use std::ptr::NonNull;
49 /// let a = &mut 42;
50 ///
51 /// let ptr = unsafe { NonNull::new_unchecked(a) };
52 /// ```
53 /// Use instead:
54 /// ```rust
55 /// use std::ptr::NonNull;
56 /// let a = &mut 42;
57 ///
a2a8927a 58 /// // SAFETY: references are guaranteed to be non-null.
3c0e092e
XL
59 /// let ptr = unsafe { NonNull::new_unchecked(a) };
60 /// ```
a2a8927a 61 #[clippy::version = "1.58.0"]
3c0e092e
XL
62 pub UNDOCUMENTED_UNSAFE_BLOCKS,
63 restriction,
64 "creating an unsafe block without explaining why it is safe"
65}
487cf647
FG
66declare_clippy_lint! {
67 /// ### What it does
68 /// Checks for `// SAFETY: ` comments on safe code.
69 ///
70 /// ### Why is this bad?
71 /// Safe code has no safety requirements, so there is no need to
72 /// describe safety invariants.
73 ///
74 /// ### Example
75 /// ```rust
76 /// use std::ptr::NonNull;
77 /// let a = &mut 42;
78 ///
79 /// // SAFETY: references are guaranteed to be non-null.
80 /// let ptr = NonNull::new(a).unwrap();
81 /// ```
82 /// Use instead:
83 /// ```rust
84 /// use std::ptr::NonNull;
85 /// let a = &mut 42;
86 ///
87 /// let ptr = NonNull::new(a).unwrap();
88 /// ```
89 #[clippy::version = "1.67.0"]
90 pub UNNECESSARY_SAFETY_COMMENT,
91 restriction,
92 "annotating safe code with a safety comment"
93}
3c0e092e 94
fe692bf9
FG
95#[derive(Copy, Clone)]
96pub struct UndocumentedUnsafeBlocks {
97 accept_comment_above_statement: bool,
98 accept_comment_above_attributes: bool,
99}
100
101impl UndocumentedUnsafeBlocks {
102 pub fn new(accept_comment_above_statement: bool, accept_comment_above_attributes: bool) -> Self {
103 Self {
104 accept_comment_above_statement,
105 accept_comment_above_attributes,
106 }
107 }
108}
109
110impl_lint_pass!(UndocumentedUnsafeBlocks => [UNDOCUMENTED_UNSAFE_BLOCKS, UNNECESSARY_SAFETY_COMMENT]);
3c0e092e 111
487cf647
FG
112impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks {
113 fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
04454e1e
FG
114 if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
115 && !in_external_macro(cx.tcx.sess, block.span)
116 && !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, block.hir_id)
923072b8 117 && !is_unsafe_from_proc_macro(cx, block.span)
487cf647 118 && !block_has_safety_comment(cx, block.span)
fe692bf9
FG
119 && !block_parents_have_safety_comment(
120 self.accept_comment_above_statement,
121 self.accept_comment_above_attributes,
122 cx,
123 block.hir_id,
124 )
04454e1e
FG
125 {
126 let source_map = cx.tcx.sess.source_map();
127 let span = if source_map.is_multiline(block.span) {
128 source_map.span_until_char(block.span, '\n')
129 } else {
130 block.span
131 };
3c0e092e 132
04454e1e
FG
133 span_lint_and_help(
134 cx,
135 UNDOCUMENTED_UNSAFE_BLOCKS,
136 span,
137 "unsafe block missing a safety comment",
138 None,
139 "consider adding a safety comment on the preceding line",
140 );
3c0e092e 141 }
487cf647
FG
142
143 if let Some(tail) = block.expr
144 && !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, tail.hir_id)
145 && !in_external_macro(cx.tcx.sess, tail.span)
146 && let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, tail.span, tail.hir_id)
147 && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, tail, pos)
148 {
149 span_lint_and_help(
150 cx,
151 UNNECESSARY_SAFETY_COMMENT,
152 tail.span,
153 "expression has unnecessary safety comment",
154 Some(help_span),
155 "consider removing the safety comment",
156 );
157 }
3c0e092e 158 }
923072b8 159
487cf647
FG
160 fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &hir::Stmt<'tcx>) {
161 let (
162 hir::StmtKind::Local(&hir::Local { init: Some(expr), .. })
163 | hir::StmtKind::Expr(expr)
164 | hir::StmtKind::Semi(expr)
165 ) = stmt.kind else { return };
166 if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, stmt.hir_id)
167 && !in_external_macro(cx.tcx.sess, stmt.span)
168 && let HasSafetyComment::Yes(pos) = stmt_has_safety_comment(cx, stmt.span, stmt.hir_id)
169 && let Some(help_span) = expr_has_unnecessary_safety_comment(cx, expr, pos)
923072b8 170 {
487cf647
FG
171 span_lint_and_help(
172 cx,
173 UNNECESSARY_SAFETY_COMMENT,
174 stmt.span,
175 "statement has unnecessary safety comment",
176 Some(help_span),
177 "consider removing the safety comment",
178 );
179 }
180 }
181
182 fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) {
183 if in_external_macro(cx.tcx.sess, item.span) {
184 return;
185 }
186
187 let mk_spans = |pos: BytePos| {
923072b8 188 let source_map = cx.tcx.sess.source_map();
487cf647
FG
189 let span = Span::new(pos, pos, SyntaxContext::root(), None);
190 let help_span = source_map.span_extend_to_next_char(span, '\n', true);
923072b8
FG
191 let span = if source_map.is_multiline(item.span) {
192 source_map.span_until_char(item.span, '\n')
193 } else {
194 item.span
195 };
487cf647
FG
196 (span, help_span)
197 };
923072b8 198
487cf647
FG
199 let item_has_safety_comment = item_has_safety_comment(cx, item);
200 match (&item.kind, item_has_safety_comment) {
201 // lint unsafe impl without safety comment
202 (hir::ItemKind::Impl(impl_), HasSafetyComment::No) if impl_.unsafety == hir::Unsafety::Unsafe => {
203 if !is_lint_allowed(cx, UNDOCUMENTED_UNSAFE_BLOCKS, item.hir_id())
204 && !is_unsafe_from_proc_macro(cx, item.span)
205 {
206 let source_map = cx.tcx.sess.source_map();
207 let span = if source_map.is_multiline(item.span) {
208 source_map.span_until_char(item.span, '\n')
209 } else {
210 item.span
211 };
212
213 span_lint_and_help(
214 cx,
215 UNDOCUMENTED_UNSAFE_BLOCKS,
216 span,
217 "unsafe impl missing a safety comment",
218 None,
219 "consider adding a safety comment on the preceding line",
220 );
221 }
222 },
223 // lint safe impl with unnecessary safety comment
224 (hir::ItemKind::Impl(impl_), HasSafetyComment::Yes(pos)) if impl_.unsafety == hir::Unsafety::Normal => {
225 if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
226 let (span, help_span) = mk_spans(pos);
227
228 span_lint_and_help(
229 cx,
230 UNNECESSARY_SAFETY_COMMENT,
231 span,
232 "impl has unnecessary safety comment",
233 Some(help_span),
234 "consider removing the safety comment",
235 );
236 }
237 },
238 (hir::ItemKind::Impl(_), _) => {},
239 // const and static items only need a safety comment if their body is an unsafe block, lint otherwise
240 (&hir::ItemKind::Const(.., body) | &hir::ItemKind::Static(.., body), HasSafetyComment::Yes(pos)) => {
241 if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) {
242 let body = cx.tcx.hir().body(body);
243 if !matches!(
244 body.value.kind, hir::ExprKind::Block(block, _)
245 if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
246 ) {
247 let (span, help_span) = mk_spans(pos);
248
249 span_lint_and_help(
250 cx,
251 UNNECESSARY_SAFETY_COMMENT,
252 span,
253 &format!("{} has unnecessary safety comment", item.kind.descr()),
254 Some(help_span),
255 "consider removing the safety comment",
256 );
257 }
258 }
259 },
260 // Aside from unsafe impls and consts/statics with an unsafe block, items in general
261 // do not have safety invariants that need to be documented, so lint those.
262 (_, HasSafetyComment::Yes(pos)) => {
263 if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, item.hir_id()) {
264 let (span, help_span) = mk_spans(pos);
265
266 span_lint_and_help(
267 cx,
268 UNNECESSARY_SAFETY_COMMENT,
269 span,
270 &format!("{} has unnecessary safety comment", item.kind.descr()),
271 Some(help_span),
272 "consider removing the safety comment",
273 );
274 }
275 },
276 _ => (),
923072b8
FG
277 }
278 }
04454e1e 279}
3c0e092e 280
487cf647
FG
281fn expr_has_unnecessary_safety_comment<'tcx>(
282 cx: &LateContext<'tcx>,
283 expr: &'tcx hir::Expr<'tcx>,
284 comment_pos: BytePos,
285) -> Option<Span> {
9ffffee4
FG
286 if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| {
287 matches!(
288 node,
289 Node::Block(&Block {
290 rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
291 ..
292 }),
293 )
294 }) {
295 return None;
296 }
297
487cf647
FG
298 // this should roughly be the reverse of `block_parents_have_safety_comment`
299 if for_each_expr_with_closures(cx, expr, |expr| match expr.kind {
300 hir::ExprKind::Block(
301 Block {
302 rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided),
303 ..
304 },
305 _,
306 ) => ControlFlow::Break(()),
307 // statements will be handled by check_stmt itself again
308 hir::ExprKind::Block(..) => ControlFlow::Continue(Descend::No),
309 _ => ControlFlow::Continue(Descend::Yes),
310 })
311 .is_some()
312 {
313 return None;
314 }
315
316 let source_map = cx.tcx.sess.source_map();
317 let span = Span::new(comment_pos, comment_pos, SyntaxContext::root(), None);
318 let help_span = source_map.span_extend_to_next_char(span, '\n', true);
319
320 Some(help_span)
321}
322
923072b8 323fn is_unsafe_from_proc_macro(cx: &LateContext<'_>, span: Span) -> bool {
04454e1e 324 let source_map = cx.sess().source_map();
923072b8 325 let file_pos = source_map.lookup_byte_offset(span.lo());
04454e1e
FG
326 file_pos
327 .sf
328 .src
329 .as_deref()
330 .and_then(|src| src.get(file_pos.pos.to_usize()..))
331 .map_or(true, |src| !src.starts_with("unsafe"))
3c0e092e
XL
332}
333
487cf647
FG
334// Checks if any parent {expression, statement, block, local, const, static}
335// has a safety comment
fe692bf9
FG
336fn block_parents_have_safety_comment(
337 accept_comment_above_statement: bool,
338 accept_comment_above_attributes: bool,
339 cx: &LateContext<'_>,
340 id: hir::HirId,
341) -> bool {
487cf647
FG
342 if let Some(node) = get_parent_node(cx.tcx, id) {
343 return match node {
fe692bf9
FG
344 Node::Expr(expr) => {
345 if let Some(
346 Node::Local(hir::Local { span, .. })
347 | Node::Item(hir::Item {
348 kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
349 span,
350 ..
351 }),
352 ) = get_parent_node(cx.tcx, expr.hir_id)
353 {
354 let hir_id = match get_parent_node(cx.tcx, expr.hir_id) {
355 Some(Node::Local(hir::Local { hir_id, .. })) => *hir_id,
356 Some(Node::Item(hir::Item { owner_id, .. })) => {
357 cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id)
358 },
359 _ => unreachable!(),
360 };
361
362 // if unsafe block is part of a let/const/static statement,
363 // and accept_comment_above_statement is set to true
364 // we accept the safety comment in the line the precedes this statement.
365 accept_comment_above_statement
366 && span_with_attrs_in_body_has_safety_comment(
367 cx,
368 *span,
369 hir_id,
370 accept_comment_above_attributes,
371 )
372 } else {
373 !is_branchy(expr)
374 && span_with_attrs_in_body_has_safety_comment(
375 cx,
376 expr.span,
377 expr.hir_id,
378 accept_comment_above_attributes,
379 )
380 }
381 },
487cf647
FG
382 Node::Stmt(hir::Stmt {
383 kind:
fe692bf9
FG
384 hir::StmtKind::Local(hir::Local { span, hir_id, .. })
385 | hir::StmtKind::Expr(hir::Expr { span, hir_id, .. })
386 | hir::StmtKind::Semi(hir::Expr { span, hir_id, .. }),
487cf647
FG
387 ..
388 })
fe692bf9
FG
389 | Node::Local(hir::Local { span, hir_id, .. }) => {
390 span_with_attrs_in_body_has_safety_comment(cx, *span, *hir_id, accept_comment_above_attributes)
391 },
392 Node::Item(hir::Item {
487cf647
FG
393 kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
394 span,
fe692bf9 395 owner_id,
487cf647 396 ..
fe692bf9
FG
397 }) => span_with_attrs_in_body_has_safety_comment(
398 cx,
399 *span,
400 cx.tcx.hir().local_def_id_to_hir_id(owner_id.def_id),
401 accept_comment_above_attributes,
402 ),
487cf647
FG
403 _ => false,
404 };
405 }
406 false
407}
408
fe692bf9
FG
409/// Extends `span` to also include its attributes, then checks if that span has a safety comment.
410fn span_with_attrs_in_body_has_safety_comment(
411 cx: &LateContext<'_>,
412 span: Span,
413 hir_id: HirId,
414 accept_comment_above_attributes: bool,
415) -> bool {
416 let span = if accept_comment_above_attributes {
417 include_attrs_in_span(cx, hir_id, span)
418 } else {
419 span
420 };
421
422 span_in_body_has_safety_comment(cx, span)
423}
424
487cf647
FG
425/// Checks if an expression is "branchy", e.g. loop, match/if/etc.
426fn is_branchy(expr: &hir::Expr<'_>) -> bool {
427 matches!(
428 expr.kind,
429 hir::ExprKind::If(..) | hir::ExprKind::Loop(..) | hir::ExprKind::Match(..)
430 )
431}
432
04454e1e 433/// Checks if the lines immediately preceding the block contain a safety comment.
487cf647 434fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
04454e1e
FG
435 // This intentionally ignores text before the start of a function so something like:
436 // ```
437 // // SAFETY: reason
438 // fn foo() { unsafe { .. } }
439 // ```
440 // won't work. This is to avoid dealing with where such a comment should be place relative to
441 // attributes and doc comments.
442
487cf647
FG
443 matches!(
444 span_from_macro_expansion_has_safety_comment(cx, span),
445 HasSafetyComment::Yes(_)
446 ) || span_in_body_has_safety_comment(cx, span)
447}
448
fe692bf9
FG
449fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span {
450 span.to(cx
451 .tcx
452 .hir()
453 .attrs(hir_id)
454 .iter()
455 .fold(span, |acc, attr| acc.to(attr.span)))
456}
457
487cf647
FG
458enum HasSafetyComment {
459 Yes(BytePos),
460 No,
461 Maybe,
923072b8
FG
462}
463
464/// Checks if the lines immediately preceding the item contain a safety comment.
465#[allow(clippy::collapsible_match)]
487cf647
FG
466fn item_has_safety_comment(cx: &LateContext<'_>, item: &hir::Item<'_>) -> HasSafetyComment {
467 match span_from_macro_expansion_has_safety_comment(cx, item.span) {
468 HasSafetyComment::Maybe => (),
469 has_safety_comment => return has_safety_comment,
923072b8
FG
470 }
471
487cf647
FG
472 if item.span.ctxt() != SyntaxContext::root() {
473 return HasSafetyComment::No;
474 }
475 if let Some(parent_node) = get_parent_node(cx.tcx, item.hir_id()) {
476 let comment_start = match parent_node {
477 Node::Crate(parent_mod) => {
478 comment_start_before_item_in_mod(cx, parent_mod, parent_mod.spans.inner_span, item)
479 },
480 Node::Item(parent_item) => {
481 if let ItemKind::Mod(parent_mod) = &parent_item.kind {
482 comment_start_before_item_in_mod(cx, parent_mod, parent_item.span, item)
483 } else {
923072b8 484 // Doesn't support impls in this position. Pretend a comment was found.
487cf647
FG
485 return HasSafetyComment::Maybe;
486 }
487 },
488 Node::Stmt(stmt) => {
489 if let Some(Node::Block(block)) = get_parent_node(cx.tcx, stmt.hir_id) {
490 walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo)
491 } else {
492 // Problem getting the parent node. Pretend a comment was found.
493 return HasSafetyComment::Maybe;
494 }
495 },
496 _ => {
497 // Doesn't support impls in this position. Pretend a comment was found.
498 return HasSafetyComment::Maybe;
499 },
500 };
923072b8 501
487cf647
FG
502 let source_map = cx.sess().source_map();
503 if let Some(comment_start) = comment_start
504 && let Ok(unsafe_line) = source_map.lookup_line(item.span.lo())
505 && let Ok(comment_start_line) = source_map.lookup_line(comment_start)
506 && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
507 && let Some(src) = unsafe_line.sf.src.as_deref()
508 {
509 return unsafe_line.sf.lines(|lines| {
510 if comment_start_line.line >= unsafe_line.line {
511 HasSafetyComment::No
512 } else {
513 match text_has_safety_comment(
923072b8
FG
514 src,
515 &lines[comment_start_line.line + 1..=unsafe_line.line],
516 unsafe_line.sf.start_pos.to_usize(),
487cf647
FG
517 ) {
518 Some(b) => HasSafetyComment::Yes(b),
519 None => HasSafetyComment::No,
520 }
521 }
522 });
523 }
524 }
525 HasSafetyComment::Maybe
526}
527
528/// Checks if the lines immediately preceding the item contain a safety comment.
529#[allow(clippy::collapsible_match)]
530fn stmt_has_safety_comment(cx: &LateContext<'_>, span: Span, hir_id: HirId) -> HasSafetyComment {
531 match span_from_macro_expansion_has_safety_comment(cx, span) {
532 HasSafetyComment::Maybe => (),
533 has_safety_comment => return has_safety_comment,
534 }
535
536 if span.ctxt() != SyntaxContext::root() {
537 return HasSafetyComment::No;
538 }
539
540 if let Some(parent_node) = get_parent_node(cx.tcx, hir_id) {
541 let comment_start = match parent_node {
542 Node::Block(block) => walk_span_to_context(block.span, SyntaxContext::root()).map(Span::lo),
543 _ => return HasSafetyComment::Maybe,
544 };
545
546 let source_map = cx.sess().source_map();
547 if let Some(comment_start) = comment_start
548 && let Ok(unsafe_line) = source_map.lookup_line(span.lo())
549 && let Ok(comment_start_line) = source_map.lookup_line(comment_start)
550 && Lrc::ptr_eq(&unsafe_line.sf, &comment_start_line.sf)
551 && let Some(src) = unsafe_line.sf.src.as_deref()
552 {
553 return unsafe_line.sf.lines(|lines| {
554 if comment_start_line.line >= unsafe_line.line {
555 HasSafetyComment::No
556 } else {
557 match text_has_safety_comment(
558 src,
559 &lines[comment_start_line.line + 1..=unsafe_line.line],
560 unsafe_line.sf.start_pos.to_usize(),
561 ) {
562 Some(b) => HasSafetyComment::Yes(b),
563 None => HasSafetyComment::No,
564 }
565 }
566 });
923072b8 567 }
923072b8 568 }
487cf647 569 HasSafetyComment::Maybe
923072b8
FG
570}
571
487cf647 572fn comment_start_before_item_in_mod(
923072b8
FG
573 cx: &LateContext<'_>,
574 parent_mod: &hir::Mod<'_>,
575 parent_mod_span: Span,
487cf647 576 item: &hir::Item<'_>,
923072b8
FG
577) -> Option<BytePos> {
578 parent_mod.item_ids.iter().enumerate().find_map(|(idx, item_id)| {
487cf647 579 if *item_id == item.item_id() {
923072b8
FG
580 if idx == 0 {
581 // mod A { /* comment */ unsafe impl T {} ... }
582 // ^------------------------------------------^ returns the start of this span
583 // ^---------------------^ finally checks comments in this range
584 if let Some(sp) = walk_span_to_context(parent_mod_span, SyntaxContext::root()) {
585 return Some(sp.lo());
586 }
587 } else {
588 // some_item /* comment */ unsafe impl T {}
589 // ^-------^ returns the end of this span
590 // ^---------------^ finally checks comments in this range
591 let prev_item = cx.tcx.hir().item(parent_mod.item_ids[idx - 1]);
592 if let Some(sp) = walk_span_to_context(prev_item.span, SyntaxContext::root()) {
593 return Some(sp.hi());
594 }
595 }
596 }
597 None
598 })
599}
600
487cf647 601fn span_from_macro_expansion_has_safety_comment(cx: &LateContext<'_>, span: Span) -> HasSafetyComment {
04454e1e 602 let source_map = cx.sess().source_map();
923072b8
FG
603 let ctxt = span.ctxt();
604 if ctxt == SyntaxContext::root() {
487cf647 605 HasSafetyComment::Maybe
923072b8
FG
606 } else {
607 // From a macro expansion. Get the text from the start of the macro declaration to start of the
608 // unsafe block.
04454e1e
FG
609 // macro_rules! foo { () => { stuff }; (x) => { unsafe { stuff } }; }
610 // ^--------------------------------------------^
923072b8 611 if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
04454e1e
FG
612 && let Ok(macro_line) = source_map.lookup_line(ctxt.outer_expn_data().def_site.lo())
613 && Lrc::ptr_eq(&unsafe_line.sf, &macro_line.sf)
614 && let Some(src) = unsafe_line.sf.src.as_deref()
615 {
923072b8 616 unsafe_line.sf.lines(|lines| {
487cf647
FG
617 if macro_line.line < unsafe_line.line {
618 match text_has_safety_comment(
619 src,
620 &lines[macro_line.line + 1..=unsafe_line.line],
621 unsafe_line.sf.start_pos.to_usize(),
622 ) {
623 Some(b) => HasSafetyComment::Yes(b),
624 None => HasSafetyComment::No,
625 }
626 } else {
627 HasSafetyComment::No
628 }
923072b8 629 })
04454e1e
FG
630 } else {
631 // Problem getting source text. Pretend a comment was found.
487cf647 632 HasSafetyComment::Maybe
3c0e092e 633 }
923072b8
FG
634 }
635}
636
064997fb
FG
637fn get_body_search_span(cx: &LateContext<'_>) -> Option<Span> {
638 let body = cx.enclosing_body?;
639 let map = cx.tcx.hir();
640 let mut span = map.body(body).value.span;
641 for (_, node) in map.parent_iter(body.hir_id) {
642 match node {
643 Node::Expr(e) => span = e.span,
fe692bf9
FG
644 Node::Block(_)
645 | Node::Arm(_)
646 | Node::Stmt(_)
647 | Node::Local(_)
648 | Node::Item(hir::Item {
649 kind: hir::ItemKind::Const(..) | ItemKind::Static(..),
650 ..
651 }) => (),
064997fb
FG
652 _ => break,
653 }
654 }
655 Some(span)
656}
657
923072b8
FG
658fn span_in_body_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
659 let source_map = cx.sess().source_map();
660 let ctxt = span.ctxt();
661 if ctxt == SyntaxContext::root()
064997fb 662 && let Some(search_span) = get_body_search_span(cx)
04454e1e 663 {
923072b8 664 if let Ok(unsafe_line) = source_map.lookup_line(span.lo())
064997fb 665 && let Some(body_span) = walk_span_to_context(search_span, SyntaxContext::root())
923072b8
FG
666 && let Ok(body_line) = source_map.lookup_line(body_span.lo())
667 && Lrc::ptr_eq(&unsafe_line.sf, &body_line.sf)
668 && let Some(src) = unsafe_line.sf.src.as_deref()
669 {
670 // Get the text from the start of function body to the unsafe block.
671 // fn foo() { some_stuff; unsafe { stuff }; other_stuff; }
672 // ^-------------^
673 unsafe_line.sf.lines(|lines| {
674 body_line.line < unsafe_line.line && text_has_safety_comment(
675 src,
676 &lines[body_line.line + 1..=unsafe_line.line],
677 unsafe_line.sf.start_pos.to_usize(),
487cf647 678 ).is_some()
923072b8
FG
679 })
680 } else {
681 // Problem getting source text. Pretend a comment was found.
682 true
683 }
04454e1e 684 } else {
923072b8 685 false
3c0e092e
XL
686 }
687}
688
04454e1e 689/// Checks if the given text has a safety comment for the immediately proceeding line.
487cf647 690fn text_has_safety_comment(src: &str, line_starts: &[BytePos], offset: usize) -> Option<BytePos> {
04454e1e
FG
691 let mut lines = line_starts
692 .array_windows::<2>()
693 .rev()
694 .map_while(|[start, end]| {
695 let start = start.to_usize() - offset;
696 let end = end.to_usize() - offset;
487cf647
FG
697 let text = src.get(start..end)?;
698 let trimmed = text.trim_start();
699 Some((start + (text.len() - trimmed.len()), trimmed))
04454e1e
FG
700 })
701 .filter(|(_, text)| !text.is_empty());
702
703 let Some((line_start, line)) = lines.next() else {
487cf647 704 return None;
04454e1e
FG
705 };
706 // Check for a sequence of line comments.
707 if line.starts_with("//") {
487cf647 708 let (mut line, mut line_start) = (line, line_start);
04454e1e
FG
709 loop {
710 if line.to_ascii_uppercase().contains("SAFETY:") {
487cf647
FG
711 return Some(BytePos(
712 u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
713 ));
04454e1e
FG
714 }
715 match lines.next() {
487cf647
FG
716 Some((s, x)) if x.starts_with("//") => (line, line_start) = (x, s),
717 _ => return None,
3c0e092e 718 }
3c0e092e 719 }
3c0e092e 720 }
04454e1e
FG
721 // No line comments; look for the start of a block comment.
722 // This will only find them if they are at the start of a line.
723 let (mut line_start, mut line) = (line_start, line);
724 loop {
725 if line.starts_with("/*") {
487cf647 726 let src = &src[line_start..line_starts.last().unwrap().to_usize() - offset];
04454e1e 727 let mut tokens = tokenize(src);
487cf647 728 return (src[..tokens.next().unwrap().len as usize]
04454e1e
FG
729 .to_ascii_uppercase()
730 .contains("SAFETY:")
487cf647
FG
731 && tokens.all(|t| t.kind == TokenKind::Whitespace))
732 .then_some(BytePos(
733 u32::try_from(line_start).unwrap() + u32::try_from(offset).unwrap(),
734 ));
3c0e092e 735 }
04454e1e
FG
736 match lines.next() {
737 Some(x) => (line_start, line) = x,
487cf647 738 None => return None,
3c0e092e
XL
739 }
740 }
741}