rustc_parse/
validate_attr.rs

1//! Meta-syntax validation logic of attributes for post-expansion.
2
3use std::slice;
4
5use rustc_ast::token::Delimiter;
6use rustc_ast::tokenstream::DelimSpan;
7use rustc_ast::{
8    self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, NodeId,
9    Path, Safety,
10};
11use rustc_attr_parsing::{AttributeParser, Late};
12use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult};
13use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
14use rustc_session::errors::report_lit_error;
15use rustc_session::lint::BuiltinLintDiag;
16use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
17use rustc_session::parse::ParseSess;
18use rustc_span::{Span, Symbol, sym};
19
20use crate::{errors, parse_in};
21
22pub fn check_attr(psess: &ParseSess, attr: &Attribute, id: NodeId) {
23    if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
24    {
25        return;
26    }
27
28    let builtin_attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
29
30    let builtin_attr_safety = builtin_attr_info.map(|x| x.safety);
31    check_attribute_safety(psess, builtin_attr_safety, attr, id);
32
33    // Check input tokens for built-in and key-value attributes.
34    match builtin_attr_info {
35        // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
36        Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => {
37            match parse_meta(psess, attr) {
38                // Don't check safety again, we just did that
39                Ok(meta) => {
40                    check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false)
41                }
42                Err(err) => {
43                    err.emit();
44                }
45            }
46        }
47        _ => {
48            let attr_item = attr.get_normal_item();
49            if let AttrArgs::Eq { .. } = attr_item.args {
50                // All key-value attributes are restricted to meta-item syntax.
51                match parse_meta(psess, attr) {
52                    Ok(_) => {}
53                    Err(err) => {
54                        err.emit();
55                    }
56                }
57            }
58        }
59    }
60}
61
62pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
63    let item = attr.get_normal_item();
64    Ok(MetaItem {
65        unsafety: item.unsafety,
66        span: attr.span,
67        path: item.path.clone(),
68        kind: match &item.args {
69            AttrArgs::Empty => MetaItemKind::Word,
70            AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
71                check_meta_bad_delim(psess, *dspan, *delim);
72                let nmis =
73                    parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
74                MetaItemKind::List(nmis)
75            }
76            AttrArgs::Eq { expr, .. } => {
77                if let ast::ExprKind::Lit(token_lit) = expr.kind {
78                    let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
79                    let res = match res {
80                        Ok(lit) => {
81                            if token_lit.suffix.is_some() {
82                                let mut err = psess.dcx().struct_span_err(
83                                    expr.span,
84                                    "suffixed literals are not allowed in attributes",
85                                );
86                                err.help(
87                                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
88                                    use an unsuffixed version (`1`, `1.0`, etc.)",
89                                );
90                                return Err(err);
91                            } else {
92                                MetaItemKind::NameValue(lit)
93                            }
94                        }
95                        Err(err) => {
96                            let guar = report_lit_error(psess, err, token_lit, expr.span);
97                            let lit = ast::MetaItemLit {
98                                symbol: token_lit.symbol,
99                                suffix: token_lit.suffix,
100                                kind: ast::LitKind::Err(guar),
101                                span: expr.span,
102                            };
103                            MetaItemKind::NameValue(lit)
104                        }
105                    };
106                    res
107                } else {
108                    // Example cases:
109                    // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`.
110                    // - `#[foo = include_str!("nonexistent-file.rs")]`:
111                    //   results in `ast::ExprKind::Err`. In that case we delay
112                    //   the error because an earlier error will have already
113                    //   been reported.
114                    let msg = "attribute value must be a literal";
115                    let mut err = psess.dcx().struct_span_err(expr.span, msg);
116                    if let ast::ExprKind::Err(_) = expr.kind {
117                        err.downgrade_to_delayed_bug();
118                    }
119                    return Err(err);
120                }
121            }
122        },
123    })
124}
125
126fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
127    if let Delimiter::Parenthesis = delim {
128        return;
129    }
130    psess.dcx().emit_err(errors::MetaBadDelim {
131        span: span.entire(),
132        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
133    });
134}
135
136pub(super) fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
137    if let Delimiter::Parenthesis = delim {
138        return;
139    }
140    psess.dcx().emit_err(errors::CfgAttrBadDelim {
141        span: span.entire(),
142        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
143    });
144}
145
146/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
147fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
148    let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
149        [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
150        _ => false,
151    };
152    match meta {
153        MetaItemKind::Word => template.word,
154        MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
155        MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
156        MetaItemKind::NameValue(..) => false,
157    }
158}
159
160pub fn check_attribute_safety(
161    psess: &ParseSess,
162    builtin_attr_safety: Option<AttributeSafety>,
163    attr: &Attribute,
164    id: NodeId,
165) {
166    let attr_item = attr.get_normal_item();
167    match (builtin_attr_safety, attr_item.unsafety) {
168        // - Unsafe builtin attribute
169        // - User wrote `#[unsafe(..)]`, which is permitted on any edition
170        (Some(AttributeSafety::Unsafe { .. }), Safety::Unsafe(..)) => {
171            // OK
172        }
173
174        // - Unsafe builtin attribute
175        // - User did not write `#[unsafe(..)]`
176        (Some(AttributeSafety::Unsafe { unsafe_since }), Safety::Default) => {
177            let path_span = attr_item.path.span;
178
179            // If the `attr_item`'s span is not from a macro, then just suggest
180            // wrapping it in `unsafe(...)`. Otherwise, we suggest putting the
181            // `unsafe(`, `)` right after and right before the opening and closing
182            // square bracket respectively.
183            let diag_span = attr_item.span();
184
185            // Attributes can be safe in earlier editions, and become unsafe in later ones.
186            //
187            // Use the span of the attribute's name to determine the edition: the span of the
188            // attribute as a whole may be inaccurate if it was emitted by a macro.
189            //
190            // See https://github.com/rust-lang/rust/issues/142182.
191            let emit_error = match unsafe_since {
192                None => true,
193                Some(unsafe_since) => path_span.edition() >= unsafe_since,
194            };
195
196            if emit_error {
197                psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe {
198                    span: path_span,
199                    suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
200                        left: diag_span.shrink_to_lo(),
201                        right: diag_span.shrink_to_hi(),
202                    },
203                });
204            } else {
205                psess.buffer_lint(
206                    UNSAFE_ATTR_OUTSIDE_UNSAFE,
207                    path_span,
208                    id,
209                    BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
210                        attribute_name_span: path_span,
211                        sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
212                    },
213                );
214            }
215        }
216
217        // - Normal builtin attribute, or any non-builtin attribute
218        // - All non-builtin attributes are currently considered safe; writing `#[unsafe(..)]` is
219        //   not permitted on non-builtin attributes or normal builtin attributes
220        (Some(AttributeSafety::Normal) | None, Safety::Unsafe(unsafe_span)) => {
221            psess.dcx().emit_err(errors::InvalidAttrUnsafe {
222                span: unsafe_span,
223                name: attr_item.path.clone(),
224            });
225        }
226
227        // - Normal builtin attribute
228        // - No explicit `#[unsafe(..)]` written.
229        (Some(AttributeSafety::Normal), Safety::Default) => {
230            // OK
231        }
232
233        // - Non-builtin attribute
234        // - No explicit `#[unsafe(..)]` written.
235        (None, Safety::Default) => {
236            // OK
237        }
238
239        (
240            Some(AttributeSafety::Unsafe { .. } | AttributeSafety::Normal) | None,
241            Safety::Safe(..),
242        ) => {
243            psess.dcx().span_delayed_bug(
244                attr_item.span(),
245                "`check_attribute_safety` does not expect `Safety::Safe` on attributes",
246            );
247        }
248    }
249}
250
251// Called by `check_builtin_meta_item` and code that manually denies
252// `unsafe(...)` in `cfg`
253pub fn deny_builtin_meta_unsafety(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) {
254    // This only supports denying unsafety right now - making builtin attributes
255    // support unsafety will requite us to thread the actual `Attribute` through
256    // for the nice diagnostics.
257    if let Safety::Unsafe(unsafe_span) = unsafety {
258        diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() });
259    }
260}
261
262pub fn check_builtin_meta_item(
263    psess: &ParseSess,
264    meta: &MetaItem,
265    style: ast::AttrStyle,
266    name: Symbol,
267    template: AttributeTemplate,
268    deny_unsafety: bool,
269) {
270    if !is_attr_template_compatible(&template, &meta.kind) {
271        // attrs with new parsers are locally validated so excluded here
272        if AttributeParser::<Late>::is_parsed_attribute(slice::from_ref(&name)) {
273            return;
274        }
275        emit_malformed_attribute(psess, style, meta.span, name, template);
276    }
277
278    if deny_unsafety {
279        deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
280    }
281}
282
283fn emit_malformed_attribute(
284    psess: &ParseSess,
285    style: ast::AttrStyle,
286    span: Span,
287    name: Symbol,
288    template: AttributeTemplate,
289) {
290    // Some of previously accepted forms were used in practice,
291    // report them as warnings for now.
292    let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
293
294    let error_msg = format!("malformed `{name}` attribute input");
295    let mut suggestions = vec![];
296    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
297    if template.word {
298        suggestions.push(format!("#{inner}[{name}]"));
299    }
300    if let Some(descr) = template.list {
301        suggestions.push(format!("#{inner}[{name}({descr})]"));
302    }
303    suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
304    if let Some(descr) = template.name_value_str {
305        suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
306    }
307    if should_warn(name) {
308        psess.buffer_lint(
309            ILL_FORMED_ATTRIBUTE_INPUT,
310            span,
311            ast::CRATE_NODE_ID,
312            BuiltinLintDiag::IllFormedAttributeInput { suggestions: suggestions.clone() },
313        );
314    } else {
315        suggestions.sort();
316        psess
317            .dcx()
318            .struct_span_err(span, error_msg)
319            .with_span_suggestions(
320                span,
321                if suggestions.len() == 1 {
322                    "must be of the form"
323                } else {
324                    "the following are the possible correct uses"
325                },
326                suggestions,
327                Applicability::HasPlaceholders,
328            )
329            .emit();
330    }
331}
332
333pub fn emit_fatal_malformed_builtin_attribute(
334    psess: &ParseSess,
335    attr: &Attribute,
336    name: Symbol,
337) -> ! {
338    let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
339    emit_malformed_attribute(psess, attr.style, attr.span, name, template);
340    // This is fatal, otherwise it will likely cause a cascade of other errors
341    // (and an error here is expected to be very rare).
342    FatalError.raise()
343}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy