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::stable
289                | sym::unstable
290                | sym::rustc_allowed_through_unstable_modules
291                | sym::rustc_specialization_trait
292                | sym::rustc_unsafe_specialization_marker
293                | sym::rustc_allow_incoherent_impl
294                | sym::rustc_coherence_is_core
295                | sym::marker
296                | sym::fundamental
297                | sym::rustc_paren_sugar
298                | sym::type_const
299                | sym::repr
300                // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres
301                // ambiguity
302                | sym::rustc_align
303                | sym::deprecated
304                | sym::optimize
305                | sym::pointee
306                | sym::cold
307                | sym::target_feature
308                | sym::rustc_allow_const_fn_unstable
309                | sym::macro_use
310                | sym::macro_escape
311                | sym::naked
312                | sym::no_mangle
313                | sym::non_exhaustive
314                | sym::omit_gdb_pretty_printer_section
315                | sym::path
316                | sym::ignore
317                | sym::must_use
318                | sym::track_caller
319                | sym::link_name
320                | sym::link_ordinal
321                | sym::export_name
322                | sym::rustc_macro_transparency
323                | sym::link_section
324                | sym::rustc_layout_scalar_valid_range_start
325                | sym::rustc_layout_scalar_valid_range_end
326                | sym::no_implicit_prelude
327                | sym::automatically_derived
328                | sym::coverage
329        ) {
330            return;
331        }
332        emit_malformed_attribute(psess, style, meta.span, name, template);
333    }
334
335    if deny_unsafety {
336        deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
337    }
338}
339
340fn emit_malformed_attribute(
341    psess: &ParseSess,
342    style: ast::AttrStyle,
343    span: Span,
344    name: Symbol,
345    template: AttributeTemplate,
346) {
347    // Some of previously accepted forms were used in practice,
348    // report them as warnings for now.
349    let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
350
351    let error_msg = format!("malformed `{name}` attribute input");
352    let mut suggestions = vec![];
353    let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
354    if template.word {
355        suggestions.push(format!("#{inner}[{name}]"));
356    }
357    if let Some(descr) = template.list {
358        suggestions.push(format!("#{inner}[{name}({descr})]"));
359    }
360    suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
361    if let Some(descr) = template.name_value_str {
362        suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
363    }
364    if should_warn(name) {
365        psess.buffer_lint(
366            ILL_FORMED_ATTRIBUTE_INPUT,
367            span,
368            ast::CRATE_NODE_ID,
369            BuiltinLintDiag::IllFormedAttributeInput { suggestions: suggestions.clone() },
370        );
371    } else {
372        suggestions.sort();
373        psess
374            .dcx()
375            .struct_span_err(span, error_msg)
376            .with_span_suggestions(
377                span,
378                if suggestions.len() == 1 {
379                    "must be of the form"
380                } else {
381                    "the following are the possible correct uses"
382                },
383                suggestions,
384                Applicability::HasPlaceholders,
385            )
386            .emit();
387    }
388}
389
390pub fn emit_fatal_malformed_builtin_attribute(
391    psess: &ParseSess,
392    attr: &Attribute,
393    name: Symbol,
394) -> ! {
395    let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
396    emit_malformed_attribute(psess, attr.style, attr.span, name, template);
397    // This is fatal, otherwise it will likely cause a cascade of other errors
398    // (and an error here is expected to be very rare).
399    FatalError.raise()
400}
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