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    Path, Safety,
8};
9use rustc_errors::{Applicability, DiagCtxtHandle, 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(diag: DiagCtxtHandle<'_>, unsafety: Safety, name: &Path) {
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) = unsafety {
255        diag.emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: name.clone() });
256    }
257}
258
259pub fn check_builtin_meta_item(
260    psess: &ParseSess,
261    meta: &MetaItem,
262    style: ast::AttrStyle,
263    name: Symbol,
264    template: AttributeTemplate,
265    deny_unsafety: bool,
266) {
267    if !is_attr_template_compatible(&template, &meta.kind) {
268        // attrs with new parsers are locally validated so excluded here
269        if matches!(
270            name,
271            sym::inline
272                | sym::export_stable
273                | sym::ffi_const
274                | sym::ffi_pure
275                | sym::rustc_std_internal_symbol
276                | sym::may_dangle
277                | sym::rustc_as_ptr
278                | sym::rustc_pub_transparent
279                | sym::rustc_const_stable_indirect
280                | sym::rustc_force_inline
281                | sym::rustc_confusables
282                | sym::rustc_skip_during_method_dispatch
283                | sym::rustc_pass_by_value
284                | sym::rustc_deny_explicit_impl
285                | sym::rustc_do_not_implement_via_object
286                | sym::rustc_coinductive
287                | sym::const_trait
288                | sym::rustc_specialization_trait
289                | sym::rustc_unsafe_specialization_marker
290                | sym::rustc_allow_incoherent_impl
291                | sym::rustc_coherence_is_core
292                | sym::marker
293                | sym::fundamental
294                | sym::rustc_paren_sugar
295                | sym::type_const
296                | sym::repr
297                // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres
298                // ambiguity
299                | sym::rustc_align
300                | sym::deprecated
301                | sym::optimize
302                | sym::pointee
303                | sym::cold
304                | sym::target_feature
305                | sym::rustc_allow_const_fn_unstable
306                | sym::macro_use
307                | sym::macro_escape
308                | sym::naked
309                | sym::no_mangle
310                | sym::non_exhaustive
311                | sym::omit_gdb_pretty_printer_section
312                | sym::path
313                | sym::ignore
314                | sym::must_use
315                | sym::track_caller
316                | sym::link_name
317                | sym::link_ordinal
318                | sym::export_name
319                | sym::rustc_macro_transparency
320                | sym::link_section
321                | sym::rustc_layout_scalar_valid_range_start
322                | sym::rustc_layout_scalar_valid_range_end
323                | sym::no_implicit_prelude
324                | sym::automatically_derived
325                | sym::coverage
326        ) {
327            return;
328        }
329        emit_malformed_attribute(psess, style, meta.span, name, template);
330    }
331
332    if deny_unsafety {
333        deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
334    }
335}
336
337fn emit_malformed_attribute(
338    psess: &ParseSess,
339    style: ast::AttrStyle,
340    span: Span,
341    name: Symbol,
342    template: AttributeTemplate,
343) {
344    // Some of previously accepted forms were used in practice,
345    // report them as warnings for now.
346    let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
347
348    let error_msg = format!("malformed `{name}` attribute input");
349    let mut suggestions = vec![];
350    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
351    if template.word {
352        suggestions.push(format!("#{inner}[{name}]"));
353    }
354    if let Some(descr) = template.list {
355        suggestions.push(format!("#{inner}[{name}({descr})]"));
356    }
357    suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
358    if let Some(descr) = template.name_value_str {
359        suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
360    }
361    if should_warn(name) {
362        psess.buffer_lint(
363            ILL_FORMED_ATTRIBUTE_INPUT,
364            span,
365            ast::CRATE_NODE_ID,
366            BuiltinLintDiag::IllFormedAttributeInput { suggestions: suggestions.clone() },
367        );
368    } else {
369        suggestions.sort();
370        psess
371            .dcx()
372            .struct_span_err(span, error_msg)
373            .with_span_suggestions(
374                span,
375                if suggestions.len() == 1 {
376                    "must be of the form"
377                } else {
378                    "the following are the possible correct uses"
379                },
380                suggestions,
381                Applicability::HasPlaceholders,
382            )
383            .emit();
384    }
385}
386
387pub fn emit_fatal_malformed_builtin_attribute(
388    psess: &ParseSess,
389    attr: &Attribute,
390    name: Symbol,
391) -> ! {
392    let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
393    emit_malformed_attribute(psess, attr.style, attr.span, name, template);
394    // This is fatal, otherwise it will likely cause a cascade of other errors
395    // (and an error here is expected to be very rare).
396    FatalError.raise()
397}
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