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