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, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety,
10};
11use rustc_errors::{Applicability, Diagnostic, PResult};
12use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, template};
13use rustc_hir::AttrPath;
14use rustc_parse::parse_in;
15use rustc_session::errors::report_lit_error;
16use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
17use rustc_session::parse::ParseSess;
18use rustc_span::{Span, Symbol, sym};
19
20use crate::{AttributeParser, session_diagnostics as errors};
21
22pub fn check_attr(psess: &ParseSess, attr: &Attribute) {
23 if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace)
24 {
25 return;
26 }
27
28 let builtin_attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name));
29
30 match builtin_attr_info {
32 Some(BuiltinAttribute { name, .. }) => {
33 if AttributeParser::is_parsed_attribute(slice::from_ref(&name)) {
34 return;
35 }
36 match parse_meta(psess, attr) {
37 Ok(meta) => {
39 let lint_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
41 if !lint_attrs.contains(name) {
::core::panicking::panic("assertion failed: lint_attrs.contains(name)")
};assert!(lint_attrs.contains(name));
42
43 let template = ::rustc_feature::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!(
44 List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
45 "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
46 );
47 check_builtin_meta_item(psess, &meta, attr.style, *name, template, false)
48 }
49 Err(err) => {
50 err.emit();
51 }
52 }
53 }
54 _ => {
55 let attr_item = attr.get_normal_item();
56 if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() {
57 match parse_meta(psess, attr) {
59 Ok(_) => {}
60 Err(err) => {
61 err.emit();
62 }
63 }
64 }
65 }
66 }
67}
68
69pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
70 let item = attr.get_normal_item();
71 Ok(MetaItem {
72 unsafety: item.unsafety,
73 span: attr.span,
74 path: item.path.clone(),
75 kind: match &item.args.unparsed_ref().unwrap() {
76 AttrArgs::Empty => MetaItemKind::Word,
77 AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
78 check_meta_bad_delim(psess, *dspan, *delim);
79 let nmis =
80 parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
81 MetaItemKind::List(nmis)
82 }
83 AttrArgs::Eq { expr, .. } => {
84 if let ast::ExprKind::Lit(token_lit) = expr.kind {
85 let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
86 let res = match res {
87 Ok(lit) => {
88 if token_lit.suffix.is_some() {
89 let mut err = psess.dcx().struct_span_err(
90 expr.span,
91 "suffixed literals are not allowed in attributes",
92 );
93 err.help(
94 "instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), \
95 use an unsuffixed version (`1`, `1.0`, etc.)",
96 );
97 return Err(err);
98 } else {
99 MetaItemKind::NameValue(lit)
100 }
101 }
102 Err(err) => {
103 let guar = report_lit_error(psess, err, token_lit, expr.span);
104 let lit = ast::MetaItemLit {
105 symbol: token_lit.symbol,
106 suffix: token_lit.suffix,
107 kind: ast::LitKind::Err(guar),
108 span: expr.span,
109 };
110 MetaItemKind::NameValue(lit)
111 }
112 };
113 res
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::errors::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}