1use rustc_ast::token::Delimiter;
4use rustc_ast::tokenstream::DelimSpan;
5use rustc_ast::{
6 self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety,
7};
8use rustc_errors::{Applicability, FatalError, PResult};
9use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
10use rustc_session::errors::report_lit_error;
11use rustc_session::lint::BuiltinLintDiag;
12use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
13use rustc_session::parse::ParseSess;
14use rustc_span::{Span, Symbol, sym};
15
16use crate::{errors, parse_in};
17
18pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
19 if attr.is_doc_comment() {
20 return;
21 }
22
23 let attr_info = attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
24 let attr_item = attr.get_normal_item();
25
26 let safety = attr_info.map(|x| x.safety).unwrap_or(AttributeSafety::Normal);
28 check_attribute_safety(psess, safety, attr);
29
30 match attr_info {
32 Some(BuiltinAttribute { name, template, .. }) if *name != sym::rustc_dummy => {
34 match parse_meta(psess, attr) {
35 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 if let AttrArgs::Eq { .. } = attr_item.args {
46 match parse_meta(psess, attr) {
48 Ok(_) => {}
49 Err(err) => {
50 err.emit();
51 }
52 }
53 }
54 }
55 }
56}
57
58pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
59 let item = attr.get_normal_item();
60 Ok(MetaItem {
61 unsafety: item.unsafety,
62 span: attr.span,
63 path: item.path.clone(),
64 kind: match &item.args {
65 AttrArgs::Empty => MetaItemKind::Word,
66 AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
67 check_meta_bad_delim(psess, *dspan, *delim);
68 let nmis =
69 parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
70 MetaItemKind::List(nmis)
71 }
72 AttrArgs::Eq { expr, .. } => {
73 if let ast::ExprKind::Lit(token_lit) = expr.kind {
74 let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
75 let res = match res {
76 Ok(lit) => {
77 if token_lit.suffix.is_some() {
78 let mut err = psess.dcx().struct_span_err(
79 expr.span,
80 "suffixed literals are not allowed in attributes",
81 );
82 err.help(
83 "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
84 use an unsuffixed version (`1`, `1.0`, etc.)",
85 );
86 return Err(err);
87 } else {
88 MetaItemKind::NameValue(lit)
89 }
90 }
91 Err(err) => {
92 let guar = report_lit_error(psess, err, token_lit, expr.span);
93 let lit = ast::MetaItemLit {
94 symbol: token_lit.symbol,
95 suffix: token_lit.suffix,
96 kind: ast::LitKind::Err(guar),
97 span: expr.span,
98 };
99 MetaItemKind::NameValue(lit)
100 }
101 };
102 res
103 } else {
104 let msg = "attribute value must be a literal";
111 let mut err = psess.dcx().struct_span_err(expr.span, msg);
112 if let ast::ExprKind::Err(_) = expr.kind {
113 err.downgrade_to_delayed_bug();
114 }
115 return Err(err);
116 }
117 }
118 },
119 })
120}
121
122fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
123 if let Delimiter::Parenthesis = delim {
124 return;
125 }
126 psess.dcx().emit_err(errors::MetaBadDelim {
127 span: span.entire(),
128 sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
129 });
130}
131
132pub(super) fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
133 if let Delimiter::Parenthesis = delim {
134 return;
135 }
136 psess.dcx().emit_err(errors::CfgAttrBadDelim {
137 span: span.entire(),
138 sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
139 });
140}
141
142fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
144 let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
145 [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
146 _ => false,
147 };
148 match meta {
149 MetaItemKind::Word => template.word,
150 MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
151 MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
152 MetaItemKind::NameValue(..) => false,
153 }
154}
155
156pub fn check_attribute_safety(psess: &ParseSess, safety: AttributeSafety, attr: &Attribute) {
157 let attr_item = attr.get_normal_item();
158
159 if safety == AttributeSafety::Unsafe {
160 if let ast::Safety::Default = attr_item.unsafety {
161 let path_span = attr_item.path.span;
162
163 let diag_span = attr_item.span();
168
169 if attr.span.at_least_rust_2024() {
170 psess.dcx().emit_err(errors::UnsafeAttrOutsideUnsafe {
171 span: path_span,
172 suggestion: errors::UnsafeAttrOutsideUnsafeSuggestion {
173 left: diag_span.shrink_to_lo(),
174 right: diag_span.shrink_to_hi(),
175 },
176 });
177 } else {
178 psess.buffer_lint(
179 UNSAFE_ATTR_OUTSIDE_UNSAFE,
180 path_span,
181 ast::CRATE_NODE_ID,
182 BuiltinLintDiag::UnsafeAttrOutsideUnsafe {
183 attribute_name_span: path_span,
184 sugg_spans: (diag_span.shrink_to_lo(), diag_span.shrink_to_hi()),
185 },
186 );
187 }
188 }
189 } else if let Safety::Unsafe(unsafe_span) = attr_item.unsafety {
190 psess.dcx().emit_err(errors::InvalidAttrUnsafe {
191 span: unsafe_span,
192 name: attr_item.path.clone(),
193 });
194 }
195}
196
197pub fn deny_builtin_meta_unsafety(psess: &ParseSess, meta: &MetaItem) {
200 if let Safety::Unsafe(unsafe_span) = meta.unsafety {
204 psess
205 .dcx()
206 .emit_err(errors::InvalidAttrUnsafe { span: unsafe_span, name: meta.path.clone() });
207 }
208}
209
210pub fn check_builtin_meta_item(
211 psess: &ParseSess,
212 meta: &MetaItem,
213 style: ast::AttrStyle,
214 name: Symbol,
215 template: AttributeTemplate,
216 deny_unsafety: bool,
217) {
218 let should_skip = |name| name == sym::cfg;
221
222 if !should_skip(name) && !is_attr_template_compatible(&template, &meta.kind) {
223 emit_malformed_attribute(psess, style, meta.span, name, template);
224 }
225
226 if deny_unsafety {
227 deny_builtin_meta_unsafety(psess, meta);
228 }
229}
230
231fn emit_malformed_attribute(
232 psess: &ParseSess,
233 style: ast::AttrStyle,
234 span: Span,
235 name: Symbol,
236 template: AttributeTemplate,
237) {
238 let should_warn = |name| {
241 matches!(name, sym::doc | sym::ignore | sym::inline | sym::link | sym::test | sym::bench)
242 };
243
244 let error_msg = format!("malformed `{name}` attribute input");
245 let mut suggestions = vec![];
246 let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
247 if template.word {
248 suggestions.push(format!("#{inner}[{name}]"));
249 }
250 if let Some(descr) = template.list {
251 suggestions.push(format!("#{inner}[{name}({descr})]"));
252 }
253 suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
254 if let Some(descr) = template.name_value_str {
255 suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
256 }
257 if should_warn(name) {
258 psess.buffer_lint(
259 ILL_FORMED_ATTRIBUTE_INPUT,
260 span,
261 ast::CRATE_NODE_ID,
262 BuiltinLintDiag::IllFormedAttributeInput { suggestions: suggestions.clone() },
263 );
264 } else {
265 suggestions.sort();
266 psess
267 .dcx()
268 .struct_span_err(span, error_msg)
269 .with_span_suggestions(
270 span,
271 if suggestions.len() == 1 {
272 "must be of the form"
273 } else {
274 "the following are the possible correct uses"
275 },
276 suggestions,
277 Applicability::HasPlaceholders,
278 )
279 .emit();
280 }
281}
282
283pub fn emit_fatal_malformed_builtin_attribute(
284 psess: &ParseSess,
285 attr: &Attribute,
286 name: Symbol,
287) -> ! {
288 let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template;
289 emit_malformed_attribute(psess, attr.style, attr.span, name, template);
290 FatalError.raise()
293}