rustc_attr_parsing/
validate_attr.rs

1//! Meta-syntax validation logic of attributes for post-expansion.
2
3use std::convert::identity;
4use std::slice;
5
6use rustc_ast::token::Delimiter;
7use rustc_ast::tokenstream::DelimSpan;
8use rustc_ast::{
9    self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety,
10};
11use rustc_errors::{Applicability, FatalError, PResult};
12use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
13use rustc_hir::AttrPath;
14use rustc_hir::lints::AttributeLintKind;
15use rustc_parse::parse_in;
16use rustc_session::errors::report_lit_error;
17use rustc_session::lint::BuiltinLintDiag;
18use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
19use rustc_session::parse::ParseSess;
20use rustc_span::{Span, Symbol, sym};
21
22use crate::{AttributeParser, Late, session_diagnostics as errors};
23
24pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
25    if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
26    {
27        return;
28    }
29
30    let builtin_attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
31
32    // Check input tokens for built-in and key-value attributes.
33    match builtin_attr_info {
34        // `rustc_dummy` doesn't have any restrictions specific to built-in attributes.
35        Some(BuiltinAttribute { name, template, .. }) => {
36            if AttributeParser::<Late>::is_parsed_attribute(slice::from_ref(&name)) {
37                return;
38            }
39            match parse_meta(psess, attr) {
40                // Don't check safety again, we just did that
41                Ok(meta) => {
42                    check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false)
43                }
44                Err(err) => {
45                    err.emit();
46                }
47            }
48        }
49        _ => {
50            let attr_item = attr.get_normal_item();
51            if let AttrArgs::Eq { .. } = attr_item.args {
52                // All key-value attributes are restricted to meta-item syntax.
53                match parse_meta(psess, attr) {
54                    Ok(_) => {}
55                    Err(err) => {
56                        err.emit();
57                    }
58                }
59            }
60        }
61    }
62}
63
64pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
65    let item = attr.get_normal_item();
66    Ok(MetaItem {
67        unsafety: item.unsafety,
68        span: attr.span,
69        path: item.path.clone(),
70        kind: match &item.args {
71            AttrArgs::Empty => MetaItemKind::Word,
72            AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
73                check_meta_bad_delim(psess, *dspan, *delim);
74                let nmis =
75                    parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
76                MetaItemKind::List(nmis)
77            }
78            AttrArgs::Eq { expr, .. } => {
79                if let ast::ExprKind::Lit(token_lit) = expr.kind {
80                    let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
81                    let res = match res {
82                        Ok(lit) => {
83                            if token_lit.suffix.is_some() {
84                                let mut err = psess.dcx().struct_span_err(
85                                    expr.span,
86                                    "suffixed literals are not allowed in attributes",
87                                );
88                                err.help(
89                                    "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
90                                    use an unsuffixed version (`1`, `1.0`, etc.)",
91                                );
92                                return Err(err);
93                            } else {
94                                MetaItemKind::NameValue(lit)
95                            }
96                        }
97                        Err(err) => {
98                            let guar = report_lit_error(psess, err, token_lit, expr.span);
99                            let lit = ast::MetaItemLit {
100                                symbol: token_lit.symbol,
101                                suffix: token_lit.suffix,
102                                kind: ast::LitKind::Err(guar),
103                                span: expr.span,
104                            };
105                            MetaItemKind::NameValue(lit)
106                        }
107                    };
108                    res
109                } else {
110                    // Example cases:
111                    // - `#[foo = 1+1]`: results in `ast::ExprKind::Binary`.
112                    // - `#[foo = include_str!("nonexistent-file.rs")]`:
113                    //   results in `ast::ExprKind::Err`. In that case we delay
114                    //   the error because an earlier error will have already
115                    //   been reported.
116                    let msg = "attribute value must be a literal";
117                    let mut err = psess.dcx().struct_span_err(expr.span, msg);
118                    if let ast::ExprKind::Err(_) = expr.kind {
119                        err.downgrade_to_delayed_bug();
120                    }
121                    return Err(err);
122                }
123            }
124        },
125    })
126}
127
128fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
129    if let Delimiter::Parenthesis = delim {
130        return;
131    }
132    psess.dcx().emit_err(errors::MetaBadDelim {
133        span: span.entire(),
134        sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
135    });
136}
137
138/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
139fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
140    let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
141        [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
142        _ => false,
143    };
144    match meta {
145        MetaItemKind::Word => template.word,
146        MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
147        MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
148        MetaItemKind::NameValue(..) => false,
149    }
150}
151
152pub fn check_builtin_meta_item(
153    psess: &ParseSess,
154    meta: &MetaItem,
155    style: ast::AttrStyle,
156    name: Symbol,
157    template: AttributeTemplate,
158    deny_unsafety: bool,
159) {
160    if !is_attr_template_compatible(&template, &meta.kind) {
161        // attrs with new parsers are locally validated so excluded here
162        emit_malformed_attribute(psess, style, meta.span, name, template);
163    }
164
165    if deny_unsafety && let Safety::Unsafe(unsafe_span) = meta.unsafety {
166        psess.dcx().emit_err(errors::InvalidAttrUnsafe {
167            span: unsafe_span,
168            name: AttrPath::from_ast(&meta.path, identity),
169        });
170    }
171}
172
173fn emit_malformed_attribute(
174    psess: &ParseSess,
175    style: ast::AttrStyle,
176    span: Span,
177    name: Symbol,
178    template: AttributeTemplate,
179) {
180    // Some of previously accepted forms were used in practice,
181    // report them as warnings for now.
182    let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
183
184    let error_msg = format!("malformed `{name}` attribute input");
185    let mut suggestions = vec![];
186    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
187    if template.word {
188        suggestions.push(format!("#{inner}[{name}]"));
189    }
190    if let Some(descr) = template.list {
191        for descr in descr {
192            suggestions.push(format!("#{inner}[{name}({descr})]"));
193        }
194    }
195    suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
196    if let Some(descr) = template.name_value_str {
197        for descr in descr {
198            suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
199        }
200    }
201    // If there are too many suggestions, better remove all of them as it's just noise at this
202    // point.
203    if suggestions.len() > 3 {
204        suggestions.clear();
205    }
206    if should_warn(name) {
207        psess.buffer_lint(
208            ILL_FORMED_ATTRIBUTE_INPUT,
209            span,
210            ast::CRATE_NODE_ID,
211            BuiltinLintDiag::AttributeLint(AttributeLintKind::IllFormedAttributeInput {
212                suggestions: suggestions.clone(),
213                docs: template.docs,
214            }),
215        );
216    } else {
217        suggestions.sort();
218        let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
219            span,
220            if suggestions.len() == 1 {
221                "must be of the form"
222            } else {
223                "the following are the possible correct uses"
224            },
225            suggestions,
226            Applicability::HasPlaceholders,
227        );
228        if let Some(link) = template.docs {
229            err.note(format!("for more information, visit <{link}>"));
230        }
231        err.emit();
232    }
233}
234
235pub fn emit_fatal_malformed_builtin_attribute(
236    psess: &ParseSess,
237    attr: &Attribute,
238    name: Symbol,
239) -> ! {
240    let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
241    emit_malformed_attribute(psess, attr.style, attr.span, name, template);
242    // This is fatal, otherwise it will likely cause a cascade of other errors
243    // (and an error here is expected to be very rare).
244    FatalError.raise()
245}