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