| use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, RtsanSetting, SanitizerSet, UsedBy}; | |
| use rustc_session::parse::feature_err; | |
| use super::prelude::*; | |
| use crate::session_diagnostics::{ | |
| NakedFunctionIncompatibleAttribute, NullOnExport, NullOnObjcClass, NullOnObjcSelector, | |
| ObjcClassExpectedStringLiteral, ObjcSelectorExpectedStringLiteral, | |
| }; | |
| use crate::target_checking::Policy::AllowSilent; | |
| pub(crate) struct OptimizeParser; | |
| impl<S: Stage> SingleAttributeParser<S> for OptimizeParser { | |
| const PATH: &[Symbol] = &[sym::optimize]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Closure), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| ]); | |
| const TEMPLATE: AttributeTemplate = template!(List: &["size", "speed", "none"]); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(list) = args.list() else { | |
| cx.expected_list(cx.attr_span, args); | |
| return None; | |
| }; | |
| let Some(single) = list.single() else { | |
| cx.expected_single_argument(list.span); | |
| return None; | |
| }; | |
| let res = match single.meta_item().and_then(|i| i.path().word().map(|i| i.name)) { | |
| Some(sym::size) => OptimizeAttr::Size, | |
| Some(sym::speed) => OptimizeAttr::Speed, | |
| Some(sym::none) => OptimizeAttr::DoNotOptimize, | |
| _ => { | |
| cx.expected_specific_argument(single.span(), &[sym::size, sym::speed, sym::none]); | |
| OptimizeAttr::Default | |
| } | |
| }; | |
| Some(AttributeKind::Optimize(res, cx.attr_span)) | |
| } | |
| } | |
| pub(crate) struct ColdParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for ColdParser { | |
| const PATH: &[Symbol] = &[sym::cold]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::ForeignFn), | |
| Allow(Target::Closure), | |
| ]); | |
| const CREATE: fn(Span) -> AttributeKind = AttributeKind::Cold; | |
| } | |
| pub(crate) struct CoverageParser; | |
| impl<S: Stage> SingleAttributeParser<S> for CoverageParser { | |
| const PATH: &[Symbol] = &[sym::coverage]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Closure), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Impl { of_trait: true }), | |
| Allow(Target::Impl { of_trait: false }), | |
| Allow(Target::Mod), | |
| Allow(Target::Crate), | |
| ]); | |
| const TEMPLATE: AttributeTemplate = template!(OneOf: &[sym::off, sym::on]); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(args) = args.list() else { | |
| cx.expected_specific_argument_and_list(cx.attr_span, &[sym::on, sym::off]); | |
| return None; | |
| }; | |
| let Some(arg) = args.single() else { | |
| cx.expected_single_argument(args.span); | |
| return None; | |
| }; | |
| let fail_incorrect_argument = | |
| |span| cx.expected_specific_argument(span, &[sym::on, sym::off]); | |
| let Some(arg) = arg.meta_item() else { | |
| fail_incorrect_argument(args.span); | |
| return None; | |
| }; | |
| let kind = match arg.path().word_sym() { | |
| Some(sym::off) => CoverageAttrKind::Off, | |
| Some(sym::on) => CoverageAttrKind::On, | |
| None | Some(_) => { | |
| fail_incorrect_argument(arg.span()); | |
| return None; | |
| } | |
| }; | |
| Some(AttributeKind::Coverage(cx.attr_span, kind)) | |
| } | |
| } | |
| pub(crate) struct ExportNameParser; | |
| impl<S: Stage> SingleAttributeParser<S> for ExportNameParser { | |
| const PATH: &[rustc_span::Symbol] = &[sym::export_name]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Static), | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Warn(Target::Field), | |
| Warn(Target::Arm), | |
| Warn(Target::MacroDef), | |
| Warn(Target::MacroCall), | |
| ]); | |
| const TEMPLATE: AttributeTemplate = template!(NameValueStr: "name"); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(nv) = args.name_value() else { | |
| cx.expected_name_value(cx.attr_span, None); | |
| return None; | |
| }; | |
| let Some(name) = nv.value_as_str() else { | |
| cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit())); | |
| return None; | |
| }; | |
| if name.as_str().contains('\0') { | |
| // `#[export_name = ...]` will be converted to a null-terminated string, | |
| // so it may not contain any null characters. | |
| cx.emit_err(NullOnExport { span: cx.attr_span }); | |
| return None; | |
| } | |
| Some(AttributeKind::ExportName { name, span: cx.attr_span }) | |
| } | |
| } | |
| pub(crate) struct RustcObjcClassParser; | |
| impl<S: Stage> SingleAttributeParser<S> for RustcObjcClassParser { | |
| const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = | |
| AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); | |
| const TEMPLATE: AttributeTemplate = template!(NameValueStr: "ClassName"); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(nv) = args.name_value() else { | |
| cx.expected_name_value(cx.attr_span, None); | |
| return None; | |
| }; | |
| let Some(classname) = nv.value_as_str() else { | |
| // `#[rustc_objc_class = ...]` is expected to be used as an implementation detail | |
| // inside a standard library macro, but `cx.expected_string_literal` exposes too much. | |
| // Use a custom error message instead. | |
| cx.emit_err(ObjcClassExpectedStringLiteral { span: nv.value_span }); | |
| return None; | |
| }; | |
| if classname.as_str().contains('\0') { | |
| // `#[rustc_objc_class = ...]` will be converted to a null-terminated string, | |
| // so it may not contain any null characters. | |
| cx.emit_err(NullOnObjcClass { span: nv.value_span }); | |
| return None; | |
| } | |
| Some(AttributeKind::RustcObjcClass { classname, span: cx.attr_span }) | |
| } | |
| } | |
| pub(crate) struct RustcObjcSelectorParser; | |
| impl<S: Stage> SingleAttributeParser<S> for RustcObjcSelectorParser { | |
| const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = | |
| AllowedTargets::AllowList(&[Allow(Target::ForeignStatic)]); | |
| const TEMPLATE: AttributeTemplate = template!(NameValueStr: "methodName"); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(nv) = args.name_value() else { | |
| cx.expected_name_value(cx.attr_span, None); | |
| return None; | |
| }; | |
| let Some(methname) = nv.value_as_str() else { | |
| // `#[rustc_objc_selector = ...]` is expected to be used as an implementation detail | |
| // inside a standard library macro, but `cx.expected_string_literal` exposes too much. | |
| // Use a custom error message instead. | |
| cx.emit_err(ObjcSelectorExpectedStringLiteral { span: nv.value_span }); | |
| return None; | |
| }; | |
| if methname.as_str().contains('\0') { | |
| // `#[rustc_objc_selector = ...]` will be converted to a null-terminated string, | |
| // so it may not contain any null characters. | |
| cx.emit_err(NullOnObjcSelector { span: nv.value_span }); | |
| return None; | |
| } | |
| Some(AttributeKind::RustcObjcSelector { methname, span: cx.attr_span }) | |
| } | |
| } | |
| #[derive(Default)] | |
| pub(crate) struct NakedParser { | |
| span: Option<Span>, | |
| } | |
| impl<S: Stage> AttributeParser<S> for NakedParser { | |
| const ATTRIBUTES: AcceptMapping<Self, S> = | |
| &[(&[sym::naked], template!(Word), |this, cx, args| { | |
| if let Err(span) = args.no_args() { | |
| cx.expected_no_args(span); | |
| return; | |
| } | |
| if let Some(earlier) = this.span { | |
| let span = cx.attr_span; | |
| cx.warn_unused_duplicate(earlier, span); | |
| } else { | |
| this.span = Some(cx.attr_span); | |
| } | |
| })]; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Warn(Target::MacroCall), | |
| ]); | |
| fn finalize(self, cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> { | |
| // FIXME(jdonszelmann): upgrade this list to *parsed* attributes | |
| // once all of these have parsed forms. That'd make the check much nicer... | |
| // | |
| // many attributes don't make sense in combination with #[naked]. | |
| // Notable attributes that are incompatible with `#[naked]` are: | |
| // | |
| // * `#[inline]` | |
| // * `#[track_caller]` | |
| // * `#[test]`, `#[ignore]`, `#[should_panic]` | |
| // | |
| // NOTE: when making changes to this list, check that `error_codes/E0736.md` remains | |
| // accurate. | |
| const ALLOW_LIST: &[rustc_span::Symbol] = &[ | |
| // conditional compilation | |
| sym::cfg_trace, | |
| sym::cfg_attr_trace, | |
| // testing (allowed here so better errors can be generated in `rustc_builtin_macros::test`) | |
| sym::test, | |
| sym::ignore, | |
| sym::should_panic, | |
| sym::bench, | |
| // diagnostics | |
| sym::allow, | |
| sym::warn, | |
| sym::deny, | |
| sym::forbid, | |
| sym::deprecated, | |
| sym::must_use, | |
| // abi, linking and FFI | |
| sym::cold, | |
| sym::export_name, | |
| sym::link_section, | |
| sym::linkage, | |
| sym::no_mangle, | |
| sym::instruction_set, | |
| sym::repr, | |
| sym::rustc_std_internal_symbol, | |
| // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity | |
| sym::rustc_align, | |
| sym::rustc_align_static, | |
| // obviously compatible with self | |
| sym::naked, | |
| // documentation | |
| sym::doc, | |
| ]; | |
| let span = self.span?; | |
| // only if we found a naked attribute do we do the somewhat expensive check | |
| 'outer: for other_attr in cx.all_attrs { | |
| for allowed_attr in ALLOW_LIST { | |
| if other_attr.segments().next().is_some_and(|i| cx.tools.contains(&i.name)) { | |
| // effectively skips the error message being emitted below | |
| // if it's a tool attribute | |
| continue 'outer; | |
| } | |
| if other_attr.word_is(*allowed_attr) { | |
| // effectively skips the error message being emitted below | |
| // if its an allowed attribute | |
| continue 'outer; | |
| } | |
| if other_attr.word_is(sym::target_feature) { | |
| if !cx.features().naked_functions_target_feature() { | |
| feature_err( | |
| &cx.sess(), | |
| sym::naked_functions_target_feature, | |
| other_attr.span(), | |
| "`#[target_feature(/* ... */)]` is currently unstable on `#[naked]` functions", | |
| ).emit(); | |
| } | |
| continue 'outer; | |
| } | |
| } | |
| cx.emit_err(NakedFunctionIncompatibleAttribute { | |
| span: other_attr.span(), | |
| naked_span: span, | |
| attr: other_attr.get_attribute_path().to_string(), | |
| }); | |
| } | |
| Some(AttributeKind::Naked(span)) | |
| } | |
| } | |
| pub(crate) struct TrackCallerParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for TrackCallerParser { | |
| const PATH: &[Symbol] = &[sym::track_caller]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Allow(Target::Method(MethodKind::Trait { body: false })), // `#[track_caller]` is inherited from trait methods | |
| Allow(Target::ForeignFn), | |
| Allow(Target::Closure), | |
| Warn(Target::MacroDef), | |
| Warn(Target::Arm), | |
| Warn(Target::Field), | |
| Warn(Target::MacroCall), | |
| ]); | |
| const CREATE: fn(Span) -> AttributeKind = AttributeKind::TrackCaller; | |
| } | |
| pub(crate) struct NoMangleParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for NoMangleParser { | |
| const PATH: &[Symbol] = &[sym::no_mangle]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Static), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| AllowSilent(Target::Const), // Handled in the `InvalidNoMangleItems` pass | |
| Error(Target::Closure), | |
| ]); | |
| const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle; | |
| } | |
| #[derive(Default)] | |
| pub(crate) struct UsedParser { | |
| first_compiler: Option<Span>, | |
| first_linker: Option<Span>, | |
| first_default: Option<Span>, | |
| } | |
| // A custom `AttributeParser` is used rather than a Simple attribute parser because | |
| // - Specifying two `#[used]` attributes is a warning (but will be an error in the future) | |
| // - But specifying two conflicting attributes: `#[used(compiler)]` and `#[used(linker)]` is already an error today | |
| // We can change this to a Simple parser once the warning becomes an error | |
| impl<S: Stage> AttributeParser<S> for UsedParser { | |
| const ATTRIBUTES: AcceptMapping<Self, S> = &[( | |
| &[sym::used], | |
| template!(Word, List: &["compiler", "linker"]), | |
| |group: &mut Self, cx, args| { | |
| let used_by = match args { | |
| ArgParser::NoArgs => UsedBy::Default, | |
| ArgParser::List(list) => { | |
| let Some(l) = list.single() else { | |
| cx.expected_single_argument(list.span); | |
| return; | |
| }; | |
| match l.meta_item().and_then(|i| i.path().word_sym()) { | |
| Some(sym::compiler) => { | |
| if !cx.features().used_with_arg() { | |
| feature_err( | |
| &cx.sess(), | |
| sym::used_with_arg, | |
| cx.attr_span, | |
| "`#[used(compiler)]` is currently unstable", | |
| ) | |
| .emit(); | |
| } | |
| UsedBy::Compiler | |
| } | |
| Some(sym::linker) => { | |
| if !cx.features().used_with_arg() { | |
| feature_err( | |
| &cx.sess(), | |
| sym::used_with_arg, | |
| cx.attr_span, | |
| "`#[used(linker)]` is currently unstable", | |
| ) | |
| .emit(); | |
| } | |
| UsedBy::Linker | |
| } | |
| _ => { | |
| cx.expected_specific_argument(l.span(), &[sym::compiler, sym::linker]); | |
| return; | |
| } | |
| } | |
| } | |
| ArgParser::NameValue(_) => return, | |
| }; | |
| let attr_span = cx.attr_span; | |
| // `#[used]` is interpreted as `#[used(linker)]` (though depending on target OS the | |
| // circumstances are more complicated). While we're checking `used_by`, also report | |
| // these cross-`UsedBy` duplicates to warn. | |
| let target = match used_by { | |
| UsedBy::Compiler => &mut group.first_compiler, | |
| UsedBy::Linker => { | |
| if let Some(prev) = group.first_default { | |
| cx.warn_unused_duplicate(prev, attr_span); | |
| return; | |
| } | |
| &mut group.first_linker | |
| } | |
| UsedBy::Default => { | |
| if let Some(prev) = group.first_linker { | |
| cx.warn_unused_duplicate(prev, attr_span); | |
| return; | |
| } | |
| &mut group.first_default | |
| } | |
| }; | |
| if let Some(prev) = *target { | |
| cx.warn_unused_duplicate(prev, attr_span); | |
| } else { | |
| *target = Some(attr_span); | |
| } | |
| }, | |
| )]; | |
| const ALLOWED_TARGETS: AllowedTargets = | |
| AllowedTargets::AllowList(&[Allow(Target::Static), Warn(Target::MacroCall)]); | |
| fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> { | |
| // If a specific form of `used` is specified, it takes precedence over generic `#[used]`. | |
| // If both `linker` and `compiler` are specified, use `linker`. | |
| Some(match (self.first_compiler, self.first_linker, self.first_default) { | |
| (_, Some(span), _) => AttributeKind::Used { used_by: UsedBy::Linker, span }, | |
| (Some(span), _, _) => AttributeKind::Used { used_by: UsedBy::Compiler, span }, | |
| (_, _, Some(span)) => AttributeKind::Used { used_by: UsedBy::Default, span }, | |
| (None, None, None) => return None, | |
| }) | |
| } | |
| } | |
| fn parse_tf_attribute<S: Stage>( | |
| cx: &mut AcceptContext<'_, '_, S>, | |
| args: &ArgParser, | |
| ) -> impl IntoIterator<Item = (Symbol, Span)> { | |
| let mut features = Vec::new(); | |
| let ArgParser::List(list) = args else { | |
| cx.expected_list(cx.attr_span, args); | |
| return features; | |
| }; | |
| if list.is_empty() { | |
| cx.warn_empty_attribute(cx.attr_span); | |
| return features; | |
| } | |
| for item in list.mixed() { | |
| let Some(name_value) = item.meta_item() else { | |
| cx.expected_name_value(item.span(), Some(sym::enable)); | |
| return features; | |
| }; | |
| // Validate name | |
| let Some(name) = name_value.path().word_sym() else { | |
| cx.expected_name_value(name_value.path().span(), Some(sym::enable)); | |
| return features; | |
| }; | |
| if name != sym::enable { | |
| cx.expected_name_value(name_value.path().span(), Some(sym::enable)); | |
| return features; | |
| } | |
| // Use value | |
| let Some(name_value) = name_value.args().name_value() else { | |
| cx.expected_name_value(item.span(), Some(sym::enable)); | |
| return features; | |
| }; | |
| let Some(value_str) = name_value.value_as_str() else { | |
| cx.expected_string_literal(name_value.value_span, Some(name_value.value_as_lit())); | |
| return features; | |
| }; | |
| for feature in value_str.as_str().split(",") { | |
| features.push((Symbol::intern(feature), item.span())); | |
| } | |
| } | |
| features | |
| } | |
| pub(crate) struct TargetFeatureParser; | |
| impl<S: Stage> CombineAttributeParser<S> for TargetFeatureParser { | |
| type Item = (Symbol, Span); | |
| const PATH: &[Symbol] = &[sym::target_feature]; | |
| const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature { | |
| features: items, | |
| attr_span: span, | |
| was_forced: false, | |
| }; | |
| const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]); | |
| fn extend( | |
| cx: &mut AcceptContext<'_, '_, S>, | |
| args: &ArgParser, | |
| ) -> impl IntoIterator<Item = Self::Item> { | |
| parse_tf_attribute(cx, args) | |
| } | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| Warn(Target::Statement), | |
| Warn(Target::Field), | |
| Warn(Target::Arm), | |
| Warn(Target::MacroDef), | |
| Warn(Target::MacroCall), | |
| ]); | |
| } | |
| pub(crate) struct ForceTargetFeatureParser; | |
| impl<S: Stage> CombineAttributeParser<S> for ForceTargetFeatureParser { | |
| type Item = (Symbol, Span); | |
| const PATH: &[Symbol] = &[sym::force_target_feature]; | |
| const CONVERT: ConvertFn<Self::Item> = |items, span| AttributeKind::TargetFeature { | |
| features: items, | |
| attr_span: span, | |
| was_forced: true, | |
| }; | |
| const TEMPLATE: AttributeTemplate = template!(List: &["enable = \"feat1, feat2\""]); | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ | |
| Allow(Target::Fn), | |
| Allow(Target::Method(MethodKind::Inherent)), | |
| Allow(Target::Method(MethodKind::Trait { body: true })), | |
| Allow(Target::Method(MethodKind::TraitImpl)), | |
| ]); | |
| fn extend( | |
| cx: &mut AcceptContext<'_, '_, S>, | |
| args: &ArgParser, | |
| ) -> impl IntoIterator<Item = Self::Item> { | |
| parse_tf_attribute(cx, args) | |
| } | |
| } | |
| pub(crate) struct SanitizeParser; | |
| impl<S: Stage> SingleAttributeParser<S> for SanitizeParser { | |
| const PATH: &[Symbol] = &[sym::sanitize]; | |
| // FIXME: still checked in check_attrs.rs | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); | |
| const TEMPLATE: AttributeTemplate = template!(List: &[ | |
| r#"address = "on|off""#, | |
| r#"kernel_address = "on|off""#, | |
| r#"cfi = "on|off""#, | |
| r#"hwaddress = "on|off""#, | |
| r#"kernel_hwaddress = "on|off""#, | |
| r#"kcfi = "on|off""#, | |
| r#"memory = "on|off""#, | |
| r#"memtag = "on|off""#, | |
| r#"shadow_call_stack = "on|off""#, | |
| r#"thread = "on|off""#, | |
| r#"realtime = "nonblocking|blocking|caller""#, | |
| ]); | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(list) = args.list() else { | |
| cx.expected_list(cx.attr_span, args); | |
| return None; | |
| }; | |
| let mut on_set = SanitizerSet::empty(); | |
| let mut off_set = SanitizerSet::empty(); | |
| let mut rtsan = None; | |
| for item in list.mixed() { | |
| let Some(item) = item.meta_item() else { | |
| cx.expected_name_value(item.span(), None); | |
| continue; | |
| }; | |
| let path = item.path().word_sym(); | |
| let Some(value) = item.args().name_value() else { | |
| cx.expected_name_value(item.span(), path); | |
| continue; | |
| }; | |
| let mut apply = |s: SanitizerSet| { | |
| let is_on = match value.value_as_str() { | |
| Some(sym::on) => true, | |
| Some(sym::off) => false, | |
| Some(_) => { | |
| cx.expected_specific_argument_strings( | |
| value.value_span, | |
| &[sym::on, sym::off], | |
| ); | |
| return; | |
| } | |
| None => { | |
| cx.expected_string_literal(value.value_span, Some(value.value_as_lit())); | |
| return; | |
| } | |
| }; | |
| if is_on { | |
| on_set |= s; | |
| } else { | |
| off_set |= s; | |
| } | |
| }; | |
| match path { | |
| Some(sym::address) | Some(sym::kernel_address) => { | |
| apply(SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS) | |
| } | |
| Some(sym::cfi) => apply(SanitizerSet::CFI), | |
| Some(sym::kcfi) => apply(SanitizerSet::KCFI), | |
| Some(sym::memory) => apply(SanitizerSet::MEMORY), | |
| Some(sym::memtag) => apply(SanitizerSet::MEMTAG), | |
| Some(sym::shadow_call_stack) => apply(SanitizerSet::SHADOWCALLSTACK), | |
| Some(sym::thread) => apply(SanitizerSet::THREAD), | |
| Some(sym::hwaddress) | Some(sym::kernel_hwaddress) => { | |
| apply(SanitizerSet::HWADDRESS | SanitizerSet::KERNELHWADDRESS) | |
| } | |
| Some(sym::realtime) => match value.value_as_str() { | |
| Some(sym::nonblocking) => rtsan = Some(RtsanSetting::Nonblocking), | |
| Some(sym::blocking) => rtsan = Some(RtsanSetting::Blocking), | |
| Some(sym::caller) => rtsan = Some(RtsanSetting::Caller), | |
| _ => { | |
| cx.expected_specific_argument_strings( | |
| value.value_span, | |
| &[sym::nonblocking, sym::blocking, sym::caller], | |
| ); | |
| } | |
| }, | |
| _ => { | |
| cx.expected_specific_argument_strings( | |
| item.path().span(), | |
| &[ | |
| sym::address, | |
| sym::kernel_address, | |
| sym::cfi, | |
| sym::kcfi, | |
| sym::memory, | |
| sym::memtag, | |
| sym::shadow_call_stack, | |
| sym::thread, | |
| sym::hwaddress, | |
| sym::kernel_hwaddress, | |
| sym::realtime, | |
| ], | |
| ); | |
| continue; | |
| } | |
| } | |
| } | |
| Some(AttributeKind::Sanitize { on_set, off_set, rtsan, span: cx.attr_span }) | |
| } | |
| } | |
| pub(crate) struct ThreadLocalParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for ThreadLocalParser { | |
| const PATH: &[Symbol] = &[sym::thread_local]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::WarnButFutureError; | |
| const ALLOWED_TARGETS: AllowedTargets = | |
| AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]); | |
| const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::ThreadLocal; | |
| } | |
| pub(crate) struct RustcPassIndirectlyInNonRusticAbisParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for RustcPassIndirectlyInNonRusticAbisParser { | |
| const PATH: &[Symbol] = &[sym::rustc_pass_indirectly_in_non_rustic_abis]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]); | |
| const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassIndirectlyInNonRusticAbis; | |
| } | |
| pub(crate) struct RustcEiiForeignItemParser; | |
| impl<S: Stage> NoArgsAttributeParser<S> for RustcEiiForeignItemParser { | |
| const PATH: &[Symbol] = &[sym::rustc_eii_foreign_item]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); | |
| const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcEiiForeignItem; | |
| } | |
| pub(crate) struct PatchableFunctionEntryParser; | |
| impl<S: Stage> SingleAttributeParser<S> for PatchableFunctionEntryParser { | |
| const PATH: &[Symbol] = &[sym::patchable_function_entry]; | |
| const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Error; | |
| const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Fn)]); | |
| const TEMPLATE: AttributeTemplate = template!(List: &["prefix_nops = m, entry_nops = n"]); | |
| fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option<AttributeKind> { | |
| let Some(meta_item_list) = args.list() else { | |
| cx.expected_list(cx.attr_span, args); | |
| return None; | |
| }; | |
| let mut prefix = None; | |
| let mut entry = None; | |
| if meta_item_list.len() == 0 { | |
| cx.expected_list(meta_item_list.span, args); | |
| return None; | |
| } | |
| let mut errored = false; | |
| for item in meta_item_list.mixed() { | |
| let Some(meta_item) = item.meta_item() else { | |
| errored = true; | |
| cx.expected_name_value(item.span(), None); | |
| continue; | |
| }; | |
| let Some(name_value_lit) = meta_item.args().name_value() else { | |
| errored = true; | |
| cx.expected_name_value(item.span(), None); | |
| continue; | |
| }; | |
| let attrib_to_write = match meta_item.ident().map(|ident| ident.name) { | |
| Some(sym::prefix_nops) => { | |
| // Duplicate prefixes are not allowed | |
| if prefix.is_some() { | |
| errored = true; | |
| cx.duplicate_key(meta_item.path().span(), sym::prefix_nops); | |
| continue; | |
| } | |
| &mut prefix | |
| } | |
| Some(sym::entry_nops) => { | |
| // Duplicate entries are not allowed | |
| if entry.is_some() { | |
| errored = true; | |
| cx.duplicate_key(meta_item.path().span(), sym::entry_nops); | |
| continue; | |
| } | |
| &mut entry | |
| } | |
| _ => { | |
| errored = true; | |
| cx.expected_specific_argument( | |
| meta_item.path().span(), | |
| &[sym::prefix_nops, sym::entry_nops], | |
| ); | |
| continue; | |
| } | |
| }; | |
| let rustc_ast::LitKind::Int(val, _) = name_value_lit.value_as_lit().kind else { | |
| errored = true; | |
| cx.expected_integer_literal(name_value_lit.value_span); | |
| continue; | |
| }; | |
| let Ok(val) = val.get().try_into() else { | |
| errored = true; | |
| cx.expected_integer_literal_in_range( | |
| name_value_lit.value_span, | |
| u8::MIN as isize, | |
| u8::MAX as isize, | |
| ); | |
| continue; | |
| }; | |
| *attrib_to_write = Some(val); | |
| } | |
| if errored { | |
| None | |
| } else { | |
| Some(AttributeKind::PatchableFunctionEntry { | |
| prefix: prefix.unwrap_or(0), | |
| entry: entry.unwrap_or(0), | |
| }) | |
| } | |
| } | |
| } |