Skip to main content

rustc_attr_parsing/
safety.rs

1use rustc_ast::Safety;
2use rustc_errors::{Diagnostic, MultiSpan};
3use rustc_hir::AttrPath;
4use rustc_lint_defs::builtin::UNSAFE_CODE;
5use rustc_session::lint::LintId;
6use rustc_session::lint::builtin::UNSAFE_ATTR_OUTSIDE_UNSAFE;
7use rustc_span::Span;
8
9use crate::attributes::AttributeSafety;
10use crate::{AttributeParser, EmitAttribute, ShouldEmit, diagnostics};
11
12impl<'sess> AttributeParser<'sess> {
13    pub fn check_attribute_safety(
14        &mut self,
15        attr_path: &AttrPath,
16        attr_span: Span,
17        attr_safety: Safety,
18        expected_safety: AttributeSafety,
19        emit_lint: &mut impl FnMut(LintId, MultiSpan, EmitAttribute),
20    ) {
21        if #[allow(non_exhaustive_omitted_patterns)] match self.should_emit {
    ShouldEmit::Nothing => true,
    _ => false,
}matches!(self.should_emit, ShouldEmit::Nothing) {
22            return;
23        }
24
25        // Check if expected & actual safety match
26        match (expected_safety, attr_safety) {
27            // - Unsafe builtin attribute
28            // - User wrote `#[unsafe(..)]`, which is permitted on any edition
29            (AttributeSafety::Unsafe { .. }, Safety::Unsafe(..)) => {
30                // OK
31            }
32
33            // - Unsafe builtin attribute
34            // - User did not write `#[unsafe(..)]`
35            (AttributeSafety::Unsafe { unsafe_since, note: _ }, Safety::Default) => {
36                let path_span = attr_path.span;
37
38                // If the `attr_item`'s span is not from a macro, then just suggest
39                // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
40                // `unsafe(`, `)` right after and right before the opening and closing
41                // square bracket respectively.
42                let diag_span = attr_span;
43
44                // Attributes can be safe in earlier editions, and become unsafe in later ones.
45                //
46                // Use the span of the attribute's name to determine the edition: the span of the
47                // attribute as a whole may be inaccurate if it was emitted by a macro.
48                //
49                // See https://github.com/rust-lang/rust/issues/142182.
50                let emit_error = match unsafe_since {
51                    None => true,
52                    Some(unsafe_since) => path_span.edition() >= unsafe_since,
53                };
54
55                let mut not_from_proc_macro = true;
56                if diag_span.from_expansion()
57                    && let Ok(mut snippet) = self.sess.source_map().span_to_snippet(diag_span)
58                {
59                    snippet.retain(|c| !c.is_whitespace());
60                    if snippet.contains("!(") || snippet.starts_with("#[") && snippet.ends_with("]")
61                    {
62                        not_from_proc_macro = false;
63                    }
64                }
65
66                if emit_error {
67                    self.emit_err(crate::session_diagnostics::UnsafeAttrOutsideUnsafe {
68                        span: path_span,
69                        suggestion: not_from_proc_macro.then(|| {
70                            crate::session_diagnostics::UnsafeAttrOutsideUnsafeSuggestion {
71                                left: diag_span.shrink_to_lo(),
72                                right: diag_span.shrink_to_hi(),
73                            }
74                        }),
75                    });
76                } else {
77                    emit_lint(
78                        LintId::of(UNSAFE_ATTR_OUTSIDE_UNSAFE),
79                        path_span.into(),
80                        EmitAttribute(Box::new(move |dcx, level, _| {
81                            diagnostics::UnsafeAttrOutsideUnsafeLint {
82                                span: path_span,
83                                suggestion: not_from_proc_macro
84                                    .then(|| (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()))
85                                    .map(|(left, right)| {
86                                        crate::session_diagnostics::UnsafeAttrOutsideUnsafeSuggestion { left, right }
87                                    }),
88                            }
89                            .into_diag(dcx, level)
90                        })),
91                    )
92                }
93            }
94
95            // - Normal builtin attribute
96            // - Writing `#[unsafe(..)]` is not permitted on normal builtin attributes
97            (AttributeSafety::Normal, Safety::Unsafe(unsafe_span)) => {
98                self.emit_err(crate::session_diagnostics::InvalidAttrUnsafe {
99                    span: unsafe_span,
100                    name: attr_path.clone(),
101                });
102            }
103
104            // - Normal builtin attribute
105            // - No explicit `#[unsafe(..)]` written.
106            (AttributeSafety::Normal, Safety::Default) => {
107                // OK
108            }
109
110            (_, Safety::Safe(..)) => {
111                self.sess.dcx().span_delayed_bug(
112                    attr_span,
113                    "`check_attribute_safety` does not expect `Safety::Safe` on attributes",
114                );
115            }
116        }
117
118        // Emit `unsafe_code` lint
119        if let AttributeSafety::Unsafe { note, .. } = expected_safety {
120            let attr_path = attr_path.clone();
121            emit_lint(
122                LintId::of(UNSAFE_CODE),
123                attr_span.into(),
124                EmitAttribute(Box::new(move |dcx, level, _| {
125                    diagnostics::UnsafeAttribute { attr_path, note }.into_diag(dcx, level)
126                })),
127            )
128        }
129    }
130}