| use rustc_hir::attrs::{CoverageAttrKind, OptimizeAttr, SanitizerSet, UsedBy}; |
| use rustc_session::parse::feature_err; |
| |
| use super::prelude::*; |
| use crate::session_diagnostics::{NakedFunctionIncompatibleAttribute, NullOnExport}; |
| |
| pub(crate) struct OptimizeParser; |
| |
| impl<S: Stage> SingleAttributeParser<S> for OptimizeParser { |
| const PATH: &[Symbol] = &[sym::optimize]; |
| const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; |
| 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); |
| 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::Trait { body: false })), |
| 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 ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; |
| 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 ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; |
| 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 }) |
| } |
| } |
| |
| #[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, |
| // 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 })), |
| 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)), |
| ]); |
| const CREATE: fn(Span) -> AttributeKind = AttributeKind::NoMangle; |
| } |
| |
| #[derive(Default)] |
| pub(crate) struct UsedParser { |
| first_compiler: Option<Span>, |
| first_linker: 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::Linker, |
| 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 target = match used_by { |
| UsedBy::Compiler => &mut group.first_compiler, |
| UsedBy::Linker => &mut group.first_linker, |
| }; |
| |
| let attr_span = cx.attr_span; |
| 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> { |
| // Ratcheting behaviour, if both `linker` and `compiler` are specified, use `linker` |
| Some(match (self.first_compiler, self.first_linker) { |
| (_, Some(span)) => AttributeKind::Used { used_by: UsedBy::Linker, span }, |
| (Some(span), _) => AttributeKind::Used { used_by: UsedBy::Compiler, span }, |
| (None, None) => return None, |
| }) |
| } |
| } |
| |
| fn parse_tf_attribute<'c, S: Stage>( |
| cx: &'c mut AcceptContext<'_, '_, S>, |
| args: &'c ArgParser<'_>, |
| ) -> impl IntoIterator<Item = (Symbol, Span)> + 'c { |
| let mut features = Vec::new(); |
| let ArgParser::List(list) = args else { |
| cx.expected_list(cx.attr_span); |
| 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<'c>( |
| cx: &'c mut AcceptContext<'_, '_, S>, |
| args: &'c ArgParser<'_>, |
| ) -> impl IntoIterator<Item = Self::Item> + 'c { |
| 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<'c>( |
| cx: &'c mut AcceptContext<'_, '_, S>, |
| args: &'c ArgParser<'_>, |
| ) -> impl IntoIterator<Item = Self::Item> + 'c { |
| 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#"kcfi = "on|off""#, |
| r#"memory = "on|off""#, |
| r#"memtag = "on|off""#, |
| r#"shadow_call_stack = "on|off""#, |
| r#"thread = "on|off""# |
| ]); |
| |
| const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepOutermost; |
| 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); |
| return None; |
| }; |
| |
| let mut on_set = SanitizerSet::empty(); |
| let mut off_set = SanitizerSet::empty(); |
| |
| 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) => apply(SanitizerSet::HWADDRESS), |
| _ => { |
| cx.expected_specific_argument_strings( |
| item.path().span(), |
| &[ |
| sym::address, |
| sym::cfi, |
| sym::kcfi, |
| sym::memory, |
| sym::memtag, |
| sym::shadow_call_stack, |
| sym::thread, |
| sym::hwaddress, |
| ], |
| ); |
| continue; |
| } |
| } |
| } |
| |
| Some(AttributeKind::Sanitize { on_set, off_set, span: cx.attr_span }) |
| } |
| } |