1use 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, AttrKind, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind,
10 Safety,
11};
12use rustc_attr_ir::AttrPath;
13use rustc_errors::{Applicability, Diagnostic, PResult};
14use rustc_feature::BUILTIN_ATTRIBUTE_SET;
15use rustc_lint_defs::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
16use rustc_parse::parse_in;
17use rustc_session::diagnostics::report_lit_error;
18use rustc_session::parse::ParseSess;
19use rustc_span::{Span, Symbol, sym};
20
21use crate::{AttributeParser, AttributeTemplate, diagnostics as errors, template};
22
23pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
24 use ast::SyntheticAttr::*;
25 match &attr.kind {
26 AttrKind::Normal(_) => {}
27 AttrKind::Synthetic(CfgTrace(_) | CfgAttrTrace(_))
28 | AttrKind::DocComment(..)
29 | AttrKind::Generated(..) => return,
30 }
31
32 let builtin_attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_SET.get(&name));
33
34 if let Some(name) = builtin_attr_info {
36 if AttributeParser::is_parsed_attribute(slice::from_ref(name)) {
37 return;
38 }
39 match parse_meta(psess, attr) {
40 Ok(meta) => {
42 let lint_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
44 if !lint_attrs.contains(name) {
::core::panicking::panic("assertion failed: lint_attrs.contains(name)")
};assert!(lint_attrs.contains(name));
45
46 let template = crate::AttributeTemplate {
word: false,
list: Some(&["lint1", "lint1, lint2, ...",
r#"lint1, lint2, lint3, reason = "...""#]),
one_of: &[],
name_value_str: None,
docs: Some("https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"),
}template!(
47 List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
48 "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
49 );
50 check_builtin_meta_item(psess, &meta, attr.style, *name, template, false)
51 }
52 Err(err) => {
53 err.emit();
54 }
55 }
56 } else {
57 let attr_item = attr.get_normal_item();
58 if let AttrArgs::Eq { .. } = attr_item.args {
59 match parse_meta(psess, attr) {
61 Ok(_) => {}
62 Err(err) => {
63 err.emit();
64 }
65 }
66 }
67 }
68}
69
70pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
71 let item = attr.get_normal_item();
72 Ok(MetaItem {
73 unsafety: item.unsafety,
74 span: attr.span,
75 path: item.path.clone(),
76 kind: match &item.args {
77 AttrArgs::Empty => MetaItemKind::Word,
78 AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
79 check_meta_bad_delim(psess, *dspan, *delim);
80 let nmis =
81 parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
82 MetaItemKind::List(nmis)
83 }
84 AttrArgs::Eq { expr, .. } => {
85 if let ast::ExprKind::Lit(token_lit) = expr.kind {
86 let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
87
88 match res {
89 Ok(lit) => {
90 if token_lit.suffix.is_some() {
91 let mut err = psess.dcx().struct_span_err(
92 expr.span,
93 "suffixed literals are not allowed in attributes",
94 );
95 err.help(
96 "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
97 use an unsuffixed version (`1`, `1.0`, etc.)",
98 );
99 return Err(err);
100 }
101 MetaItemKind::NameValue(lit)
102 }
103 Err(err) => {
104 let guar = report_lit_error(psess, err, token_lit, expr.span);
105 let lit = ast::MetaItemLit {
106 symbol: token_lit.symbol,
107 suffix: token_lit.suffix,
108 kind: ast::LitKind::Err(guar),
109 span: expr.span,
110 };
111 MetaItemKind::NameValue(lit)
112 }
113 }
114 } else {
115 let msg = "attribute value must be a literal";
122 let mut err = psess.dcx().struct_span_err(expr.span, msg);
123 if let ast::ExprKind::Err(_) = expr.kind {
124 err.downgrade_to_delayed_bug();
125 }
126 return Err(err);
127 }
128 }
129 },
130 })
131}
132
133fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
134 if let Delimiter::Parenthesis = delim {
135 return;
136 }
137 psess.dcx().emit_err(errors::MetaBadDelim {
138 span: span.entire(),
139 sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
140 });
141}
142
143fn 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_builtin_meta_item(
158 psess: &ParseSess,
159 meta: &MetaItem,
160 style: ast::AttrStyle,
161 name: Symbol,
162 template: AttributeTemplate,
163 deny_unsafety: bool,
164) {
165 if !is_attr_template_compatible(&template, &meta.kind) {
166 emit_malformed_attribute(psess, style, meta.span, name, template);
168 }
169
170 if deny_unsafety && let Safety::Unsafe(unsafe_span) = meta.unsafety {
171 psess.dcx().emit_err(errors::InvalidAttrUnsafe {
172 span: unsafe_span,
173 name: AttrPath::from_ast(&meta.path, identity),
174 });
175 }
176}
177
178pub fn emit_malformed_attribute(
179 psess: &ParseSess,
180 style: ast::AttrStyle,
181 span: Span,
182 name: Symbol,
183 template: AttributeTemplate,
184) {
185 let should_warn = |name| #[allow(non_exhaustive_omitted_patterns)] match name {
sym::doc | sym::link | sym::test | sym::bench => true,
_ => false,
}matches!(name, sym::doc | sym::link | sym::test | sym::bench);
188
189 let error_msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("malformed `{0}` attribute input",
name))
})format!("malformed `{name}` attribute input");
190 let mut suggestions = ::alloc::vec::Vec::new()vec![];
191 let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
192 if template.word {
193 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}]", inner, name))
})format!("#{inner}[{name}]"));
194 }
195 if let Some(descr) = template.list {
196 for descr in descr {
197 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
descr))
})format!("#{inner}[{name}({descr})]"));
198 }
199 }
200 suggestions.extend(template.one_of.iter().map(|&word| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
word))
})format!("#{inner}[{name}({word})]")));
201 if let Some(descr) = template.name_value_str {
202 for descr in descr {
203 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1} = \"{2}\"]", inner, name,
descr))
})format!("#{inner}[{name} = \"{descr}\"]"));
204 }
205 }
206 if suggestions.len() > 3 {
209 suggestions.clear();
210 }
211 if should_warn(name) {
212 let suggestions = suggestions.clone();
213 psess.dyn_buffer_lint(
214 ILL_FORMED_ATTRIBUTE_INPUT,
215 span,
216 ast::CRATE_NODE_ID,
217 move |dcx, level| {
218 crate::diagnostics::IllFormedAttributeInput::new(&suggestions, template.docs, None)
219 .into_diag(dcx, level)
220 },
221 );
222 } else {
223 suggestions.sort();
224 let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
225 span,
226 if suggestions.len() == 1 {
227 "must be of the form"
228 } else {
229 "the following are the possible correct uses"
230 },
231 suggestions,
232 Applicability::HasPlaceholders,
233 );
234 if let Some(link) = template.docs {
235 err.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("for more information, visit <{0}>",
link))
})format!("for more information, visit <{link}>"));
236 }
237 err.emit();
238 }
239}