Skip to content

Commit 5bae434

Browse files
Rework illformed attribute check for unparsed attributes
Signed-off-by: Jonathan Brouwer <[email protected]>
1 parent a9fb610 commit 5bae434

20 files changed

+477
-441
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3321,6 +3321,7 @@ dependencies = [
33213321
"rustc_hir",
33223322
"rustc_lexer",
33233323
"rustc_macros",
3324+
"rustc_parse",
33243325
"rustc_session",
33253326
"rustc_span",
33263327
"thin-vec",

compiler/rustc_attr_parsing/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rustc_fluent_macro = { path = "../rustc_fluent_macro" }
1515
rustc_hir = { path = "../rustc_hir" }
1616
rustc_lexer = { path = "../rustc_lexer" }
1717
rustc_macros = { path = "../rustc_macros" }
18+
rustc_parse = { path = "../rustc_parse" }
1819
rustc_session = { path = "../rustc_session" }
1920
rustc_span = { path = "../rustc_span" }
2021
thin-vec = "0.2.12"

compiler/rustc_attr_parsing/src/attributes/stability.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ pub(crate) fn parse_stability<S: Stage>(
237237
let mut feature = None;
238238
let mut since = None;
239239

240-
for param in args.list()?.mixed() {
240+
let ArgParser::List(list) = args else {
241+
cx.expected_list(cx.attr_span);
242+
return None;
243+
};
244+
245+
for param in list.mixed() {
241246
let param_span = param.span();
242247
let Some(param) = param.meta_item() else {
243248
cx.emit_err(session_diagnostics::UnsupportedLiteral {
@@ -312,7 +317,13 @@ pub(crate) fn parse_unstability<S: Stage>(
312317
let mut is_soft = false;
313318
let mut implied_by = None;
314319
let mut old_name = None;
315-
for param in args.list()?.mixed() {
320+
321+
let ArgParser::List(list) = args else {
322+
cx.expected_list(cx.attr_span);
323+
return None;
324+
};
325+
326+
for param in list.mixed() {
316327
let Some(param) = param.meta_item() else {
317328
cx.emit_err(session_diagnostics::UnsupportedLiteral {
318329
span: param.span(),

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use private::Sealed;
77
use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId};
88
use rustc_attr_data_structures::AttributeKind;
99
use rustc_attr_data_structures::lints::{AttributeLint, AttributeLintKind};
10-
use rustc_errors::{DiagCtxtHandle, Diagnostic};
11-
use rustc_feature::{AttributeTemplate, Features};
10+
use rustc_errors::{Applicability, DiagCtxtHandle, Diagnostic};
11+
use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features};
1212
use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, HirId};
13+
use rustc_parse::validate_attr::{is_attr_template_compatible, parse_meta};
1314
use rustc_session::Session;
1415
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
1516

@@ -197,7 +198,7 @@ mod private {
197198
#[allow(private_interfaces)]
198199
pub trait Stage: Sized + 'static + Sealed {
199200
type Id: Copy;
200-
const SHOULD_EMIT_LINTS: bool;
201+
const IS_LATE: bool;
201202

202203
fn parsers() -> &'static group_type!(Self);
203204

@@ -212,7 +213,7 @@ pub trait Stage: Sized + 'static + Sealed {
212213
#[allow(private_interfaces)]
213214
impl Stage for Early {
214215
type Id = NodeId;
215-
const SHOULD_EMIT_LINTS: bool = false;
216+
const IS_LATE: bool = false;
216217

217218
fn parsers() -> &'static group_type!(Self) {
218219
&early::ATTRIBUTE_PARSERS
@@ -234,7 +235,7 @@ impl Stage for Early {
234235
#[allow(private_interfaces)]
235236
impl Stage for Late {
236237
type Id = HirId;
237-
const SHOULD_EMIT_LINTS: bool = true;
238+
const IS_LATE: bool = true;
238239

239240
fn parsers() -> &'static group_type!(Self) {
240241
&late::ATTRIBUTE_PARSERS
@@ -284,7 +285,7 @@ impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> {
284285
/// must be delayed until after HIR is built. This method will take care of the details of
285286
/// that.
286287
pub(crate) fn emit_lint(&mut self, lint: AttributeLintKind, span: Span) {
287-
if !S::SHOULD_EMIT_LINTS {
288+
if !S::IS_LATE {
288289
return;
289290
}
290291
let id = self.target_id;
@@ -740,12 +741,25 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
740741
ast::AttrKind::Normal(n) => {
741742
attr_paths.push(PathParser::Ast(&n.item.path));
742743

744+
// Parse attribute using new infra
743745
let parser = MetaItemParser::from_attr(n, self.dcx());
744746
let path = parser.path();
745747
let args = parser.args();
746748
let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
747749

748750
if let Some(accepts) = S::parsers().0.get(parts.as_slice()) {
751+
//FIXME we call this to generate a few of the errors (such as invalid literals) that the new parses does not generate yet
752+
//Remove this when possible
753+
754+
// rustc_dummy is not checked unless it is of the `Eq` arguments form
755+
let skip_parse_meta_check = attr.has_name(sym::rustc_dummy)
756+
&& !matches!(n.item.args, rustc_ast::AttrArgs::Eq { .. });
757+
if !skip_parse_meta_check
758+
&& let Err(err) = parse_meta(&self.sess.psess, attr)
759+
{
760+
err.emit();
761+
}
762+
749763
for (template, accept) in accepts {
750764
let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
751765
shared: SharedContext {
@@ -777,6 +791,46 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
777791
// "attribute {path} wasn't parsed and isn't a know tool attribute",
778792
// );
779793

794+
if S::IS_LATE
795+
&& !attr.has_name(sym::cfg_trace)
796+
&& !attr.has_name(sym::cfg_attr_trace)
797+
{
798+
let builtin_attr_info = attr
799+
.ident()
800+
.and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name));
801+
if let Some(BuiltinAttribute { name, template, .. }) = builtin_attr_info
802+
{
803+
match parse_meta(&self.sess.psess, attr) {
804+
// Don't check safety again, we just did that
805+
Ok(meta) => {
806+
if !is_attr_template_compatible(&template, &meta.kind) {
807+
self.emit_malformed_unparsed_attribute(
808+
attr.style,
809+
meta.span,
810+
*name,
811+
*template,
812+
target_id,
813+
&mut emit_lint,
814+
);
815+
}
816+
}
817+
Err(err) => {
818+
err.emit();
819+
}
820+
}
821+
} else {
822+
if let rustc_ast::AttrArgs::Eq { .. } = n.item.args {
823+
// All key-value attributes are restricted to meta-item syntax.
824+
match parse_meta(&self.sess.psess, attr) {
825+
Ok(_) => {}
826+
Err(err) => {
827+
err.emit();
828+
}
829+
}
830+
}
831+
}
832+
}
833+
780834
attributes.push(Attribute::Unparsed(Box::new(AttrItem {
781835
path: AttrPath::from_ast(&n.item.path),
782836
args: self.lower_attr_args(&n.item.args, lower_span),
@@ -809,6 +863,57 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
809863
attributes
810864
}
811865

866+
fn emit_malformed_unparsed_attribute(
867+
&self,
868+
style: ast::AttrStyle,
869+
span: Span,
870+
name: Symbol,
871+
template: AttributeTemplate,
872+
target_id: S::Id,
873+
emit_lint: &mut impl FnMut(AttributeLint<S::Id>),
874+
) {
875+
// Some of previously accepted forms were used in practice,
876+
// report them as warnings for now.
877+
let should_warn = |name| matches!(name, sym::doc | sym::link | sym::test | sym::bench);
878+
879+
let error_msg = format!("malformed `{name}` attribute input");
880+
let mut suggestions = vec![];
881+
let inner = if style == ast::AttrStyle::Inner { "!" } else { "" };
882+
if template.word {
883+
suggestions.push(format!("#{inner}[{name}]"));
884+
}
885+
if let Some(descr) = template.list {
886+
suggestions.push(format!("#{inner}[{name}({descr})]"));
887+
}
888+
suggestions.extend(template.one_of.iter().map(|&word| format!("#{inner}[{name}({word})]")));
889+
if let Some(descr) = template.name_value_str {
890+
suggestions.push(format!("#{inner}[{name} = \"{descr}\"]"));
891+
}
892+
if should_warn(name) {
893+
emit_lint(AttributeLint {
894+
id: target_id,
895+
span,
896+
kind: AttributeLintKind::IllFormedAttributeInput { suggestions },
897+
});
898+
} else {
899+
suggestions.sort();
900+
self.sess
901+
.dcx()
902+
.struct_span_err(span, error_msg)
903+
.with_span_suggestions(
904+
span,
905+
if suggestions.len() == 1 {
906+
"must be of the form"
907+
} else {
908+
"the following are the possible correct uses"
909+
},
910+
suggestions,
911+
Applicability::HasPlaceholders,
912+
)
913+
.emit();
914+
}
915+
}
916+
812917
/// Returns whether there is a parser for an attribute with this name
813918
pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
814919
Late::parsers().0.contains_key(path)

compiler/rustc_builtin_macros/src/cfg_accessible.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ impl MultiItemModifier for Expander {
5151
ast::AttrStyle::Outer,
5252
sym::cfg_accessible,
5353
template,
54-
true,
5554
);
5655

5756
let Some(path) = validate_input(ecx, meta_item) else {

compiler/rustc_builtin_macros/src/derive.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ impl MultiItemModifier for Expander {
4242
ast::AttrStyle::Outer,
4343
sym::derive,
4444
template,
45-
true,
4645
);
4746

4847
let mut resolutions = match &meta_item.kind {

compiler/rustc_builtin_macros/src/util.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaI
2222
AttrStyle::Outer,
2323
name,
2424
template,
25-
true,
2625
);
2726
}
2827

compiler/rustc_parse/src/validate_attr.rs

Lines changed: 3 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use rustc_ast::{
77
Path, Safety,
88
};
99
use rustc_errors::{Applicability, DiagCtxtHandle, FatalError, PResult};
10-
use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute};
10+
use rustc_feature::{AttributeSafety, AttributeTemplate, BUILTIN_ATTRIBUTE_MAP};
1111
use rustc_session::errors::report_lit_error;
1212
use rustc_session::lint::BuiltinLintDiag;
1313
use rustc_session::lint::builtin::{ILL_FORMED_ATTRIBUTE_INPUT, UNSAFE_ATTR_OUTSIDE_UNSAFE};
@@ -26,34 +26,6 @@ pub fn check_attr(psess: &ParseSess, attr: &Attribute, id: NodeId) {
2626

2727
let builtin_attr_safety = builtin_attr_info.map(|x| x.safety);
2828
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-
}
5729
}
5830

5931
pub fn parse_meta<'a>(psess: &'a ParseSess, attr: &Attribute) -> PResult<'a, MetaItem> {
@@ -141,7 +113,7 @@ pub(super) fn check_cfg_attr_bad_delim(psess: &ParseSess, span: DelimSpan, delim
141113
}
142114

143115
/// Checks that the given meta-item is compatible with this `AttributeTemplate`.
144-
fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
116+
pub fn is_attr_template_compatible(template: &AttributeTemplate, meta: &ast::MetaItemKind) -> bool {
145117
let is_one_allowed_subword = |items: &[MetaItemInner]| match items {
146118
[item] => item.is_word() && template.one_of.iter().any(|&word| item.has_name(word)),
147119
_ => false,
@@ -262,70 +234,11 @@ pub fn check_builtin_meta_item(
262234
style: ast::AttrStyle,
263235
name: Symbol,
264236
template: AttributeTemplate,
265-
deny_unsafety: bool,
266237
) {
267238
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-
| sym::align
298-
| sym::deprecated
299-
| sym::optimize
300-
| sym::cold
301-
| sym::target_feature
302-
| sym::rustc_allow_const_fn_unstable
303-
| sym::naked
304-
| sym::no_mangle
305-
| sym::non_exhaustive
306-
| sym::omit_gdb_pretty_printer_section
307-
| sym::path
308-
| sym::ignore
309-
| sym::must_use
310-
| sym::track_caller
311-
| sym::link_name
312-
| sym::link_ordinal
313-
| sym::export_name
314-
| sym::rustc_macro_transparency
315-
| sym::link_section
316-
| sym::rustc_layout_scalar_valid_range_start
317-
| sym::rustc_layout_scalar_valid_range_end
318-
| sym::no_implicit_prelude
319-
| sym::automatically_derived
320-
) {
321-
return;
322-
}
323239
emit_malformed_attribute(psess, style, meta.span, name, template);
324240
}
325-
326-
if deny_unsafety {
327-
deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
328-
}
241+
deny_builtin_meta_unsafety(psess.dcx(), meta.unsafety, &meta.path);
329242
}
330243

331244
fn emit_malformed_attribute(

0 commit comments

Comments
 (0)