rustc_parse/
validate_attr.rs

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