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