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_errors::{Applicability, Diagnostic, PResult};
13use rustc_feature::BUILTIN_ATTRIBUTE_MAP;
14use rustc_hir::AttrPath;
15use rustc_parse::parse_in;
16use rustc_session::diagnostics::report_lit_error;
17use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT;
18use rustc_session::parse::ParseSess;
19use rustc_span::{Span, Symbol, sym};
20
21use crate::{AttributeParser, AttributeTemplate, session_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) | AttrKind::DocComment(..) => return,
28 }
29
30 let builtin_attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name));
31
32 if let Some(name) = builtin_attr_info {
34 if AttributeParser::is_parsed_attribute(slice::from_ref(name)) {
35 return;
36 }
37 match parse_meta(psess, attr) {
38 Ok(meta) => {
40 let lint_attrs = [sym::forbid, sym::allow, sym::warn, sym::deny, sym::expect];
42 if !lint_attrs.contains(name) {
::core::panicking::panic("assertion failed: lint_attrs.contains(name)")
};assert!(lint_attrs.contains(name));
43
44 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!(
45 List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#],
46 "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes"
47 );
48 check_builtin_meta_item(psess, &meta, attr.style, *name, template, false)
49 }
50 Err(err) => {
51 err.emit();
52 }
53 }
54 } else {
55 let attr_item = attr.get_normal_item();
56 if let AttrArgs::Eq { .. } = attr_item.args {
57 match parse_meta(psess, attr) {
59 Ok(_) => {}
60 Err(err) => {
61 err.emit();
62 }
63 }
64 }
65 }
66}
67
68pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
69 let item = attr.get_normal_item();
70 Ok(MetaItem {
71 unsafety: item.unsafety,
72 span: attr.span,
73 path: item.path.clone(),
74 kind: match &item.args {
75 AttrArgs::Empty => MetaItemKind::Word,
76 AttrArgs::Delimited(DelimArgs { dspan, delim, tokens }) => {
77 check_meta_bad_delim(psess, *dspan, *delim);
78 let nmis =
79 parse_in(psess, tokens.clone(), "meta list", |p| p.parse_meta_seq_top())?;
80 MetaItemKind::List(nmis)
81 }
82 AttrArgs::Eq { expr, .. } => {
83 if let ast::ExprKind::Lit(token_lit) = expr.kind {
84 let res = ast::MetaItemLit::from_token_lit(token_lit, expr.span);
85
86 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 }
99 MetaItemKind::NameValue(lit)
100 }
101 Err(err) => {
102 let guar = report_lit_error(psess, err, token_lit, expr.span);
103 let lit = ast::MetaItemLit {
104 symbol: token_lit.symbol,
105 suffix: token_lit.suffix,
106 kind: ast::LitKind::Err(guar),
107 span: expr.span,
108 };
109 MetaItemKind::NameValue(lit)
110 }
111 }
112 } else {
113 let msg = "attribute value must be a literal";
120 let mut err = psess.dcx().struct_span_err(expr.span, msg);
121 if let ast::ExprKind::Err(_) = expr.kind {
122 err.downgrade_to_delayed_bug();
123 }
124 return Err(err);
125 }
126 }
127 },
128 })
129}
130
131fn check_meta_bad_delim(psess: &ParseSess, span: DelimSpan, delim: Delimiter) {
132 if let Delimiter::Parenthesis = delim {
133 return;
134 }
135 psess.dcx().emit_err(errors::MetaBadDelim {
136 span: span.entire(),
137 sugg: errors::MetaBadDelimSugg { open: span.open, close: span.close },
138 });
139}
140
141fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
143 let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
144 [item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
145 _ => false,
146 };
147 match meta {
148 MetaItemKind::Word => template.word,
149 MetaItemKind::List(items) => template.list.is_some() || is_one_allowed_subword(items),
150 MetaItemKind::NameValue(lit) if lit.kind.is_str() => template.name_value_str.is_some(),
151 MetaItemKind::NameValue(..) => false,
152 }
153}
154
155pub fn check_builtin_meta_item(
156 psess: &ParseSess,
157 meta: &MetaItem,
158 style: ast::AttrStyle,
159 name: Symbol,
160 template: AttributeTemplate,
161 deny_unsafety: bool,
162) {
163 if !is_attr_template_compatible(&template, &meta.kind) {
164 emit_malformed_attribute(psess, style, meta.span, name, template);
166 }
167
168 if deny_unsafety && let Safety::Unsafe(unsafe_span) = meta.unsafety {
169 psess.dcx().emit_err(errors::InvalidAttrUnsafe {
170 span: unsafe_span,
171 name: AttrPath::from_ast(&meta.path, identity),
172 });
173 }
174}
175
176pub fn emit_malformed_attribute(
177 psess: &ParseSess,
178 style: ast::AttrStyle,
179 span: Span,
180 name: Symbol,
181 template: AttributeTemplate,
182) {
183 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);
186
187 let error_msg = ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("malformed `{0}` attribute input",
name))
})format!("malformed `{name}` attribute input");
188 let mut suggestions = ::alloc::vec::Vec::new()vec![];
189 let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
190 if template.word {
191 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}]", inner, name))
})format!("#{inner}[{name}]"));
192 }
193 if let Some(descr) = template.list {
194 for descr in descr {
195 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1}({2})]", inner, name,
descr))
})format!("#{inner}[{name}({descr})]"));
196 }
197 }
198 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})]")));
199 if let Some(descr) = template.name_value_str {
200 for descr in descr {
201 suggestions.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("#{0}[{1} = \"{2}\"]", inner, name,
descr))
})format!("#{inner}[{name} = \"{descr}\"]"));
202 }
203 }
204 if suggestions.len() > 3 {
207 suggestions.clear();
208 }
209 if should_warn(name) {
210 let suggestions = suggestions.clone();
211 psess.dyn_buffer_lint(
212 ILL_FORMED_ATTRIBUTE_INPUT,
213 span,
214 ast::CRATE_NODE_ID,
215 move |dcx, level| {
216 crate::diagnostics::IllFormedAttributeInput::new(&suggestions, template.docs, None)
217 .into_diag(dcx, level)
218 },
219 );
220 } else {
221 suggestions.sort();
222 let mut err = psess.dcx().struct_span_err(span, error_msg).with_span_suggestions(
223 span,
224 if suggestions.len() == 1 {
225 "must be of the form"
226 } else {
227 "the following are the possible correct uses"
228 },
229 suggestions,
230 Applicability::HasPlaceholders,
231 );
232 if let Some(link) = template.docs {
233 err.note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("for more information, visit <{0}>",
link))
})format!("for more information, visit <{link}>"));
234 }
235 err.emit();
236 }
237}